std::is_invocable_v<Callable, Args...>
is the way to go. Unfortunatelly, it will not compile just like that with if constexpr
. It will either fail because "there is no operator()()
overload", or there is no overload for operator taking Args...
.
I suggest you add a wrapper class for a callable and use it with a specialized alias template of std::variant
instead of writing your own visitor. It will allow you to use std::visit
seamlessly.
#include <type_traits>
#include <variant>
template <typename Callable>
class wrapped_callable
{
Callable c;
public:
wrapped_callable(Callable c)
: c(c)
{}
template <typename ... Args>
constexpr decltype(auto) operator()(Args &&... args) const
{
return _invoke(std::is_invocable<Callable, Args...>{}, c, std::forward<Args>(args)...);
}
private:
using _invocable = std::true_type;
using _non_invocable = std::false_type;
template <typename T, typename ... Args>
constexpr static decltype(auto) _invoke(_invocable, const T& t, Args &&... args)
{
return t(std::forward<Args>(args)...);
}
template <typename T, typename ... Args>
constexpr static decltype(auto) _invoke(_non_invocable, const T& t, Args ... args)
{
return t();
}
};
template <typename ... T>
using variant_callable = std::variant<wrapped_callable<T>...>;
struct int_callable
{
int operator()(int i) const
{
return i;
}
};
struct non_callable
{
int operator()() const
{
return 42;
}
};
#include <iostream>
int main()
{
using variant_t = variant_callable<int_callable, non_callable>;
// 23 is ignored, 42 is printed
std::visit([](const auto &callable){
std::cout << callable(23) << '\n';
}, variant_t{non_callable()});
// 23 is passed along and printed
std::visit([](const auto &callable){
std::cout << callable(23) << '\n';
}, variant_t{int_callable()});
}
Program returned: 0
42
23
https://godbolt.org/z/e6GzvW6n6
But The idea is not to have any specialization for all types in a variant as it will then require changing the visitor code every time a new type is added.
That is what template alias of std::variant<wrapped_callable<T>...>
for. You just add append a new type to the list, that's it.
Take notice, that it does not depend on if constexpr
. So if you manage to provide your own variant
and is_invocable_v
, it will work for C++14. For C++11 possibly, but some modifications regarding constexpr
functions might be needed.
Of course you can implement your visitor
in the same manner if you want to use std::shared_ptr
istead of a callable.
But I don't see any reason to use:
- visitor + smart pointer. Just use a smart pointer - it will give you runtime polymorphism in a "classic" way (via virtual inheritence)
- why
std::shared_ptr
? Do you really need to share the ownership? Just stick with std::unique_ptr