I implemented a Visit function (on a variant) that checks that the currently active type in the variant matches the function signature (more precisely the first argument). Based on this nice answer. For example
#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
}
This works, but it explodes with a user unfriendly compile time error when users passes a generic (e.g. [](auto&){}
) lambda. Is there a way to detect this and give nice static_assert()
about it?
Would also be nice if it worked with function templates as well, not just with lambdas.
Note that I do not know what possible lambdas do, so I can not do some clever stuff with Dummy types since lambdas may invoke arbitrary functions on types.
In other words I can not try to call lambda in 2 std::void_t
tests on int
and std::string
and if it works assume it is generic because they might try to call .BlaLol()
on int
and string
.
The C++ 11 standard introduced a feature named static_assert() which can be used to test a software assertion at the compile time. Syntax : static_assert( constant_expression, string_literal ); Parameters : constant_expression : An integral constant expression that can be converted to a Boolean.
If we want to initialize a lambda as a global variable, we could initialize it like this: However, this is initialized at program start-up, so we can run into problems with static initialization order fiasco. One of ways to workaround this is to initialize it at compile-time instead. We could try using constexpr to do this:
The size passed to it is ‘2’, which fails the condition to be checked, therefore producing the compile time error, and thus halting the compilation process. What are the advantages of static_assert over #error?
This works because the false branch is never taken, so just a nullptr will be returned, but the type will still be deduced as a pointer to the lambda. However, now we just have a null pointer to our lambda which in not very useful.
Is there a way to detect this and give nice static_assert about it?
I suppose you can use SFINAE over operator()
type.
Follows an example
#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), "!" );
}
Instead of a bool
, you can return std::true_type
(from foo()
first version) or std::false_type
(from second version) if you want to use it through decltype()
.
Would also be nice if it worked with function templates as well, not just with lambdas.
I don't think it's possible in a so simple way: a lambda (also a generic lambda) is an object; a template function isn't an object but a set of objects. You can pass an object to a function, not a set of objects.
But the preceding solution should works also for classes/structs with operator()
s: when there is a single, non template, operator()
, you should get 1
from foo()
; otherwise (no operator()
, more than one operator()
, template operator()
), foo()
should return 0
.
Yet another simpler option:
#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);
...
}
The Auto
class is just an invented type impossible to occur in the V
parameters. If V
accepts Auto
as an argument it must be a generic.
I tested in coliru and I can confirm the solution covers these cases:
Visit([](auto x){}); // nice static assert
Visit([](auto *x){}); // nice static assert
Visit([](auto &x){}); // nice static assert
Visit([](auto &&x){}); // nice static assert
I'm not sure if that would cover all the possible lambdas that you don't know which are :)
#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
}
I don't know if it's you want, but you can, with that static_assert if an auto lambda is passed as parameter.
I think it's not possible to do the same for template function, but not sure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With