Возможно ли для static_assert, что лямбда не является универсальной? - PullRequest
10 голосов
/ 03 апреля 2019

Я реализовал функцию 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&){}) лямбду. Есть ли способ обнаружить это и дать хороший static_assert() об этом? Было бы также неплохо, если бы он работал с шаблонами функций, а не только с лямбдами.

Обратите внимание, что я не знаю, что делают возможные лямбды, поэтому я не могу делать некоторые умные вещи с типами Dummy, так как лямбды могут вызывать произвольные функции для типов. Другими словами, я не могу попытаться вызвать лямбду в 2 std::void_t тестах на int и std::string, и если это работает, предположим, что это универсально, потому что они могут попытаться вызвать .BlaLol() на int и string.

Ответы [ 3 ]

11 голосов
/ 03 апреля 2019

Есть ли способ обнаружить это и дать хороший static_assert об этом?

Полагаю, вы можете использовать SFINAE поверх operator() type.

Следует примеру

#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_type (из второй версии), если хотите использовать его через decltype().

Было бы также неплохо, если бы он работал и с шаблонами функций, а не только с лямбдами.

Не думаю, что это возможно так просто:лямбда (также общая лямбда) является объектом;функция шаблона - это не объект, а набор объектов.Вы можете передать объект в функцию, а не в набор объектов.

Но предыдущее решение должно работать и для классов / структур с operator() s: когда есть один не шаблон, operator(), вы должны получить 1 от foo();в противном случае (нет operator(), более одного operator(), шаблон operator()), foo() должно возвращать 0.

4 голосов
/ 03 апреля 2019

Еще одна более простая опция:

#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

Я не уверен, охватит ли это все возможные лямбды, которые выне знаю, что есть :)

3 голосов
/ 03 апреля 2019
#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
}

Я не знаю, хотите ли вы этого, но вы можете с этим static_assert, если в качестве параметра передана автоматическая лямбда.

Я думаю, что невозможно сделать то же самое для функции шаблона, но не уверен.

...