Check if function return type is the same as an STL container type value

Viewed 119

I'm working with a struct that takes a generic function and a generic STL container, but i want to make a type check in the constructor in order to raise an error if the return type of the function is different from the constructor type: is it possible to do something like this without changing the template?

template<class Function, class Container>
struct task{
        Function f;
        Container& c;

        task(Function func, Container& cont):f(func), c(cont){
                //error if mismatch between container type and function return type
        }
}; 

int multiply(int x){ return x*10; }

int main(){
        vector<int> v;
        int c=10;
        auto stateless = [](float x){ return x*10;};
        auto stateful = [&c](int x){ return x*c;};

        task t(multiply, v); //SAME TYPE: OKAY!
        task tt(stateless, v); //TYPE MISMATCH: ERROR!

        return 0;
}

thank you for your help

3 Answers

Not sure to understand completely but... if the "generic funcion" isn't a generic-lambda or a template operator() in a class/struct... you tagged C++17 so you can use deduction guides so you can deduce the type returned from the function using std::function's deduction guides.

Something as

decltype(std::function{std::declval<Function>()})::result_type

For the value type of the container is usually available the value_type type.

So, defining a couple of using types inside the body of the struct, you can write

template <typename F, typename C>
struct task
 {
   using rtype = typename decltype(std::function{std::declval<F>()})::result_type;
   using vtype = typename C::value_type;

   // ...

   task (F func, C & cont) : f{func}, c{cont}
    { static_assert( std::is_same<rtype, vtype>{} );}
 }; 

But observe that the static_assert() inside the constructor use only elements that aren't specific of the constructor.

This way, if you have to develop (by example) ten constructors, you have to write ten times the same static_assert() inside the ten constructors bodies.

I suggest to place the static_assert() inside the body of the struct so you have to write it only one time.

I mean

template <typename F, typename C>
struct task
 {
   using rtype = typename decltype(std::function{std::declval<F>()})::result_type;
   using vtype = typename C::value_type;

   static_assert( std::is_same<rtype, vtype>{} );

   // ...
 }; 

The following is a full compiling example

#include <vector>
#include <functional>

template <typename F, typename C>
struct task
 {
   using rtype = typename decltype(std::function{std::declval<F>()})::result_type;
   using vtype = typename C::value_type;

   static_assert( std::is_same<rtype, vtype>{} );

   F   f;
   C & c;

   task (F func, C & cont) : f{func}, c{cont}
    { }
 }; 

int multiply (int x)
 { return x*10; }

int main ()
 {
   std::vector<int> v;

   int c=10;

   auto stateless = [](float x){ return x*10;};
   auto stateful  = [&c](int x){ return x*c;};

   task t1(multiply, v);  // compile
   task t2(stateful, v);  // compile
   task t3(stateless, v); // compilation error
 }

But remember: this function doen't works with generic-lambdas.

In that case I don't know how to solve the problem and I suppose isn't solvable at all without knowing the type of the input parameters.

You can use static_assert with std::is_same to check type equality at compile time.

If your lambda function always takes no parameters, you can use decltype(f()) to get the function return type, else you will need std::result_of / std::invoke_result or a function traits implementation.

#include <type_traits>

template<class Function, class Container>
struct task{
        Function f;
        Container& c;

        task(Function func, Container& cont):f(func), c(cont){
                static_assert(
                        std::is_same<
                                decltype(f()),                 // type of function return value
                                typename Container::value_type // type of values stored in container
                        >::value,
                        "incompatible function" // error message
                );
        }
};

I see no way to go ahead without using any kind of helper template to determine the parameter list here!

So the following solution is still based on Is it possible to figure out the parameter type and return type of a lambda?

For having function pointers and callable classes like lambdas, it only needs an specialized template instance.

template <typename CLASS>
struct function_traits_impl
: public function_traits_impl<decltype(&CLASS::operator())>
{};

template <typename CLASS, typename RET, typename... ARGS>
struct function_traits_impl< RET(CLASS::*)(ARGS...) const>
{
    using args_type = std::tuple<ARGS...>;
    using ret_type = RET;
};

template <typename CALLABLE > struct function_traits: public    function_traits_impl< CALLABLE >{};

template< typename RET, typename... ARGS >
struct function_traits< RET(*)(ARGS...) >
{
    using args_type = std::tuple<ARGS...>;
    using ret_type = RET;
};


template < typename CLASS, typename CONTAINER, typename RET, typename ... ARGS> struct task;
template< typename CLASS, typename CONTAINER, typename RET, typename ... ARGS >
struct task< CLASS, CONTAINER, RET, std::tuple<ARGS...> >
{
    using FUNC = std::function< RET(ARGS...)>;

    FUNC func;
    CONTAINER cont;

    task(  FUNC _func,  CONTAINER& _cont): func{_func}, cont{_cont}
    {
        static_assert(
            std::is_same<
            //decltype( func( std::declval<PARMS>()...) ), // but is already known from given template parms!
            RET,
            typename CONTAINER::value_type
            >::value,
            "wrong return type, did not match with container type"
            );

    }
};

template <typename FUNC, typename CONTAINER >
task(FUNC, CONTAINER) -> task< FUNC, CONTAINER, typename function_traits<FUNC>::ret_type, typename function_traits<FUNC>::args_type>;



int Any( int ) { return 0; }
float WrongAny( int, int ) { return 1.1; }

int main()
{
    std::vector<int> v;
    //task t1{ [](int, int)->float { return 0; } , v}; // fails with assert as expected
    task t2{ [](int, int)->int { return 0; } , v}; //Works!
    task t3{ &Any , v}; // Works
    //task t4{ &WrongAny, v }; fails as expected
}

This solution simply uses user defined deduction guide to forward the found parms from the trait which is helpful as you also use c++17.

Hint: Generic lambdas cant be used, because if the parameters to call the lambda are unknown, how you could determine the parameters "automatically". It is quite easy to specify the parameters with the call and get the return type, but passing an generic lambda or an object with overloaded call operator needs to specify which of the functions/methods are should be used. So if you need generic lambdas or overloaded methods in class objects simply specify params manually! There can't be a trick in any language which allows you to give a set of optional calls and determine automatically which call should be used if no other information is available. As said: If params for the call are present, simply use them!

Remark: If you use this solution, you only get a single template instance for all calls with same parameter set to the function call which may save some memory ;) But it uses a std::function to store teh callable which takes some runtime... You have now two solutions which differs in the results but both are usable ;)

Related