【问题标题】:Check if function return type is the same as an STL container type value检查函数返回类型是否与 STL 容器类型值相同
【发布时间】:2018-10-27 22:31:41
【问题描述】:

我正在使用一个采用通用函数和通用 STL 容器的结构,但我想在构造函数中进行类型检查,以便在函数的返回类型与构造函数不同时引发错误type:是否可以在不更改模板的情况下执行此类操作?

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

感谢您的帮助

【问题讨论】:

  • 函数的参数是...?
  • @max66 我添加了更多上下文;可以是带参数也可以不带参数的函数

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


【解决方案1】:

不确定是否完全理解,但是...如果“通用函数”不是类/结构中的通用 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
 }

但请记住:此函数不适用于 generic-lambdas。

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

【讨论】:

  • 很高兴看到您只需将 declvall 与 std::function 一起使用并将可调用对象作为模板参数转发。好的! +1
  • @Klaus - 是的,C++17 演绎指南非常有用。
【解决方案2】:

您可以使用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
                );
        }
};

【讨论】:

  • 你能告诉我如何在不使用函数特征实现的情况下使用带参数的函数吗?(我在问题中添加了更多上下文)
  • @joyfantastic “不使用该功能特征”。什么意思?
  • @Klaus 不使用此解决方案stackoverflow.com/questions/7943525/…
  • 对于具有任意参数列表的函数,您需要像链接解决方案中那样包含函数特征实现,或者您需要更改模板以包含参数类型(请参阅@Klaus 的解决方案)跨度>
【解决方案3】:

如果不使用任何类型的帮助模板来确定此处的参数列表,我认为无法继续!

所以下面的解决方案还是基于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 或重载方法,只需手动指定参数!任何语言都不会有任何技巧可以让您给出一组可选调用,并在没有其他信息可用的情况下自动确定应该使用哪个调用。如前所述:如果调用的参数存在,只需使用它们!

备注: 如果您使用此解决方案,您只会为所有具有相同参数的函数调用设置一个模板实例,这可能会节省一些内存;)但它使用 std::function 来存储可调用对象,这需要一些运行时间......您现在有两种结果不同但都可用的解决方案;)

【讨论】:

  • 这样我的任务结构虽然不能接受 lambda 函数
  • @joyfantastic: 新尝试 ;) 它有效,但仍然基于特征示例
  • "如果不使用任何类型的帮助模板来确定此处的参数列表,我认为无法继续!" - 在 C++17 之前正确(我想),但是 OP 标记为 C++17,在 C++17 中,您可以以更简单的方式使用std::function 的推导指南(请参阅我的答案以获取示例)。
  • @Klaus 效果很好,但现在我不能持有“标准”函数,因为它们没有 operator() 啊哈哈!正如我所说,我想同时使用 lambdas 和函数
  • @joyfantastic - Klaus 修改了他的答案,现在也适用于 lambdas(但不幸的是,不适用于通用 lamdas(毕竟作为我的解决方案))
猜你喜欢
  • 2018-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 2016-06-09
  • 2015-12-12
  • 1970-01-01
相关资源
最近更新 更多