10

我实现了一个 Visit 函数(在变体上),它检查变体中当前活动的类型是否与函数签名(更准确地说是第一个参数)匹配。基于这个不错的答案。例如

#include <variant>
#include <string>
#include <iostream>

template<typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...) const);

template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);

template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));

std::variant<int, std::string> data="abc";
template <typename V>
void Visit(V v){
using Arg1 = typename std::remove_const_t<std::remove_reference_t<first_argument<V>>>;//... TMP magic to get 1st argument of visitor + remove cvr, see Q 43526647
if (! std::holds_alternative<Arg1>(data)) {
std::cerr<< "alternative mismatch\n";
return;
}
v(std::get<Arg1>(data));
}
int main(){
    Visit([](const int& i){std::cout << i << "\n"; });
    Visit([](const std::string& s){std::cout << s << "\n"; });
    // Visit([](auto& x){}); ugly kabooom
}

这可行,但是当用户传递通用(例如[](auto&){})lambda 时,它会因用户不友好的编译时错误而爆炸。有没有办法检测到这一点并给予好评static_assert()?如果它也可以与函数模板一起使用,而不仅仅是 lambdas,那就太好了。

请注意,我不知道 lambda 可以做什么,所以我不能用 Dummy 类型做一些聪明的事情,因为 lambda 可能会调用类型上的任意函数。换句话说,我不能尝试在 2std::void_t次测试中调用 lambdaint并且std::string如果它有效,则假设它是通用的,因为他们可能会尝试.BlaLol()调用intand string

4

3 回答 3

11

有没有办法检测到这一点并给出很好的 static_assert ?

我想你可以在operator()类型上使用 SFINAE。

跟随一个例子

#include <type_traits>

template <typename T>
constexpr auto foo (T const &)
   -> decltype( &T::operator(), bool{} )
 { return true; }

constexpr bool foo (...)
 { return false; }

int main()
 {
   auto l1 = [](int){ return 0; };
   auto l2 = [](auto){ return 0; };

   static_assert( foo(l1), "!" );
   static_assert( ! foo(l2), "!" );
 }

如果bool您想std::true_type通过. _foo()std::false_typedecltype()

如果它也可以与函数模板一起使用,而不仅仅是 lambdas,那就太好了。

我认为不可能以如此简单的方式实现:lambda(也是通用 lambda)是一个对象;模板函数不是一个对象,而是一组对象。您可以将对象传递给函数,而不是一组对象。

但是前面的解决方案也适用于带有operator()s 的类/结构:当有一个非模板时operator(),你应该1foo(); 否则(不operator(),超过一个operator(),模板operator()),foo()应该返回0

于 2019-04-03T08:06:27.530 回答
4

还有一个更简单的选择:

#include <type_traits>
...
template <typename V>
void Visit(V v) {
   class Auto {};
   static_assert(!std::is_invocable<V, Auto&>::value);
   static_assert(!std::is_invocable<V, Auto*>::value);
   ...
}

该类Auto只是一种不可能出现在V参数中的发明类型。如果V接受Auto作为参数,它必须是通用的。

我在coliru中进行了测试,我可以确认解决方案涵盖了这些情况:

Visit([](auto x){}); // nice static assert
Visit([](auto *x){}); // nice static assert
Visit([](auto &x){}); // nice static assert
Visit([](auto &&x){}); // nice static assert

我不确定这是否会涵盖您不知道的所有可能的 lambdas :)

于 2019-04-03T08:56:17.040 回答
3
#include <variant>
#include <string>
#include <iostream>

template <class U, typename T = void>
struct can_be_checked : public std::false_type {};

template <typename U>
struct can_be_checked<U, std::enable_if_t< std::is_function<U>::value > >  :  public std::true_type{};

template <typename U>
struct can_be_checked<U, std::void_t<decltype(&U::operator())>> :  public std::true_type{};


template<typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...) const);

template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);

template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));

std::variant<int, std::string> data="abc";


template <typename V>
void Visit(V v){
    if constexpr ( can_be_checked<std::remove_pointer_t<decltype(v)>>::value )
    {
        using Arg1 = typename std::remove_const_t<std::remove_reference_t<first_argument<V>>>;//... TMP magic to get 1st argument of visitor + remove cvr, see Q 43526647
        if (! std::holds_alternative<Arg1>(data)) 
        {
            std::cerr<< "alternative mismatch\n";
            return;
        }
        v(std::get<Arg1>(data));
    }
    else
    {
        std::cout << "it's a template / auto lambda " << std::endl;
    }


}

template <class T>
void foo(const T& t)
{
    std::cout <<t << " foo \n";
}

void fooi(const int& t)
{
    std::cout <<t << " fooi " << std::endl;
}

int main(){
    Visit([](const int& i){std::cout << i << std::endl; });
    Visit([](const std::string& s){std::cout << s << std::endl; });
    Visit([](auto& x){std::cout <<x << std::endl;}); // it's a template / auto lambda*/
    Visit(foo<int>);

    Visit<decltype(fooi)>(fooi);
    Visit(fooi);                 


    // Visit(foo); // => fail ugly
}

我不知道它是否是您想要的,但是如果将自动 lambda 作为参数传递,则可以使用该 static_assert 。

我认为不可能对模板功能做同样的事情,但不确定。

于 2019-04-03T08:12:16.510 回答