@vsoftco answered "gcc is right to reject your code". I agree.
To fix, I say do this:
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply:std::false_type{};
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t = typename voider<Ts...>::type;
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
This is a can_apply
library that makes this kind of SFINAE simple.
Now writing one of these traits is as simple as:
template<class T>
using dot_size_r = decltype( std::declval<T>().size() );
template<class T>
using has_dot_size = can_apply< dot_size_r, T >;
test code:
int main() {
static_assert( has_dot_size<std::vector<int>>{}, "TRUE" );
static_assert( !has_dot_size<int>{}, "FALSE" );
}
Live example.
In C++17 you can move over to less declval filled expressions.
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
template<class F>
constexpr auto can_invoke(F&&) {
return [](auto&&...args) {
return std::is_invocable< F(decltype(args)...) >{};
};
}
can_invoke
takes a function f
and returns a "invokation tester". The invokation tester takes arguments, then returns true_type
if those arguments would be valid to pass to f
, and false_type
otherwise.
RETURNS
makes it easy to make a single-statement lambda SFINAE friendly. And in C++17, the lambda's operations are constexpr if possible (which is why we need C++17 here).
Then, this gives us:
template<class T>
constexpr auto can_dot_size(T&& t) {
return can_invoke([](auto&& x) RETURNS(x.size()))(t);
}
Now, often we are doing this because we want to call .size()
if possible, and otherwise return 0.
template<class T, class A, class...Bs>
decltype(auto) call_first_valid(T&& t, A&& a, Bs&&...bs) {
if constexpr( can_invoke(std::forward<A>(a))(std::forward<T>(t)) ) {
return std::forward<A>(a)(std::forward<T>(t));
else
return call_first_valid(std::forward<T>(t), std::forward<Bs>(bs)...);
}
now we can
template<class T>
std::size_t size_at_least( T&& t ) {
return call_first_valid( std::forward<T>(t),
[](auto&& t) RETURNS(t.size()),
[](auto&&)->std::size_t { return 0; }
);
}
As it happens, @Barry has proposed a feature in C++20 that replaces [](auto&& f) RETURNS(f.size())
with [](auto&& f)=>f.size()
(and more).