检查函数返回类型是否与STL容器类型值相同

时间:2018-10-27 22:31:42

标签: c++ templates stl c++17 template-meta-programming

我正在使用具有泛型函数和泛型STL容器的结构,但是我想在构造函数中进行类型检查,以在函数的返回类型不同于构造函数时引发错误。类型:是否可以在不更改模板的情况下执行类似的操作?

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;
}

感谢您的帮助

3 个答案:

答案 0 :(得分:3)

不确定是否完全理解,但是...如果“通用函数”不是通用lambda或类/结构中的模板operator() ...则标记了C ++ 17,因此您可以使用推论指南,以便您可以使用std::function的推论指南推论从函数返回的类型。

类似

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

对于容器的值类型,通常可以使用value_type类型。

因此,在结构体内部定义几个using类型,就可以编写

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>{} );}
 }; 

但是请注意,构造函数内部的static_assert()仅使用不是构造函数特定的元素。

这样,如果您必须开发(例如)十个构造函数,则必须在十个构造函数主体中编写相同的static_assert()十倍。

我建议将static_assert()放置在结构体中,这样您只需编写一次即可。

我的意思是

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>{} );

   // ...
 }; 

以下是完整的编译示例

#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
 }

但是请记住:此函数不适用于泛型lambda。

在那种情况下,我不知道如何解决该问题,并且我想在不知道输入参数类型的情况下根本无法解决。

答案 1 :(得分:1)

您可以将static_assertstd::is_same一起使用,以在编译时检查类型是否相等。

如果您的lambda函数始终不带参数,则可以使用decltype(f()) 获取函数返回类型,否则您将需要 std::result_of / std::invoke_resultfunction 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
                );
        }
};

答案 2 :(得分:1)

如果不使用任何类型的帮助模板来确定参数列表,我将无法继续进行下去!

因此以下解决方案仍基于Is it possible to figure out the parameter type and return type of a lambda?

对于具有函数指针和lambdas之类的可调用类,它只需要一个专门的模板实例。

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
}

此解决方案仅使用用户定义的推导指南就可以从特征中转发找到的参数,这对您也使用c ++ 17很有帮助。

提示: 不能使用通用lambda,因为如果调用lambda的参数未知,则如何“自动”确定参数。在调用中指定参数并获取返回类型非常容易,但是传递通用lambda或带有重载调用运算符的对象时,需要指定应使用哪些函数/方法。因此,如果您需要类对象中的通用lambda或重载方法,只需手动指定params!任何语言都不会有技巧,它允许您给出一组可选呼叫并在没有其他可用信息的情况下自动确定应使用哪个呼叫。如前所述:如果存在通话参数,只需使用它们即可!

备注: 如果您使用此解决方案,则对于具有相同参数设置的所有调用,您只会获得一个模板实例到函数调用,这可能会节省一些内存;)但它使用std :: function存储teh可调用,这会花费一些运行时间...您现在有两种解决方案,它们的结果有所不同,但是都可以使用;)