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

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

本文关键字:类型 STL 函数 返回类型 是否 检查      更新时间:2023-10-16

我正在处理一个接受泛型函数和泛型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;
}

谢谢你的帮助

不一定完全理解,但。。。如果"泛型函数"不是类/结构中的泛型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。

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

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

如果lambda函数总是不带参数,则可以使用decltype(f())若要获得函数返回类型,则需要CCD_ 11或功能特性实现。

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

如果不使用任何类型的辅助模板来确定这里的参数列表,我看不出有什么办法!

所以下面的解决方案仍然基于是否可以计算出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
}

这个解决方案只是使用用户定义的推导指南来转发从特征中找到的parm,这很有帮助,因为您也使用c++17。

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

备注:如果您使用此解决方案,则对于所有对函数调用设置了相同参数的调用,您只能获得一个模板实例,这可能会节省一些内存;(但它使用std::函数来存储可调用的内容,这需要一些运行时间。。。您现在有两个结果不同的解决方案,但都是可用的;(