Detect Operator Support with Decltype/Sfinae

Detect operator support with decltype/SFINAE

You need to make your less_than_test function a template, since SFINAE stands for Substitution Failure Is Not An Error and there's no template function that can fail selection in your code.

template <class T>
struct supports_less_than
{
template <class U>
static auto less_than_test(const U* u) -> decltype(*u < *u, char(0))
{ }

static std::array<char, 2> less_than_test(...) { }

static const bool value = (sizeof(less_than_test((T*)0)) == 1);
};

int main()
{
std::cout << std::boolalpha << supports_less_than<std::string>::value << endl;
}

SFINAE check for operator+=

I would write the second form as:

template<typename T>
auto foo(T a, T b, ...) -> decltype( a+=b, void() )
{
a += b;
}

The deduced type for decltype(a+=b, void()) would be just void if the expression a+=b is valid, else it would result in SFINAE.

Well, even in the first form, I would use the trailing-return type approach.

SFINAE: decltype on operator[]

If you want to detect whether a type has a certain function or overloaded operator you have to call that function or operator. This is important because you might have several overloads of a function or operator and overload resolution always depends on the caller.

Here is a small example, based on CppCon 2014: Walter E. Brown "Modern Template Metaprogramming: A Compendium, Part II" on how to detect operator[] in a type.

I have no idea why VC is giving you such a weird error which looks more like a parsing error. I would have expected something like »reference to overloaded function could not be resolved; did you mean to call it?«.

#include <string>
#include <type_traits>
#include <vector>

// in C++17 std::void_t
template < typename... >
using void_t = void;

template < typename T, typename Index >
using subscript_t = decltype(std::declval<T>()[std::declval<Index>()]);

template < typename, typename Index = size_t, typename = void_t<> >
struct has_subscript : std::false_type {};

template < typename T, typename Index >
struct has_subscript< T, Index, void_t< subscript_t<T,Index> > > : std::true_type {};

struct A
{
void operator[](size_t) {}
};

struct B {};

int main ()
{
static_assert(has_subscript< std::vector<int> >::value == true , "!");
static_assert(has_subscript< std::vector<double> >::value == true , "!");
static_assert(has_subscript< A >::value == true , "!");
static_assert(has_subscript< A, std::string >::value == false, "!");
static_assert(has_subscript< B >::value == false, "!");
static_assert(has_subscript< double[5] >::value == true , "!");
static_assert(has_subscript< double* >::value == true , "!");
static_assert(has_subscript< double >::value == false, "!");
}

How to use sfinae to check, whether type has operator ()?

Encouraged by a positive feedback, I will post a brief answer here.

The problem is that you cannot define a class twice, but you did this two times :-

template <typename T>
struct function_traits { .... some code ..... }

C++ doesn't allow that.

To check whether a function is exist, there is already a question about it, you can modify it to support the operator().

Here is a demo, very-slightly modified from Nicola Bonelli's answer there.

#include <iostream>

struct Hello
{
void operator()() { }
};

struct Generic {};

// SFINAE test
template <typename T>
class has_parenthesis
{
typedef char one;
typedef long two;

template <typename C> static one test( decltype(&C::operator()) ) ;
template <typename C> static two test(...);

public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
std::cout << has_parenthesis<Hello>::value << std::endl; //print 1
std::cout << has_parenthesis<Generic>::value << std::endl; //print 0
return 0;
}

I just knew a few minutes ago that it also works for operator().

Edit: I changed typeof to decltype as StoryTeller and LmTinyToon recommended. Thank.

SFINAE decltype comma operator trick

The primary issue here AFAICS is that you are using the run-time reference as a constexpr function parameter. Replacing this works just fine.

#include <iostream>

// culled by SFINAE if foo does not exist
template<typename T>
constexpr auto has_foo(int) -> decltype(std::declval<T>().foo, bool())
{
return true;
}
// catch-all fallback for items with no foo
template<typename T> constexpr bool has_foo(...)
{
return false;
}
//-----------------------------------------------------

template<typename T, bool>
struct GetFoo
{
static int value(T& t)
{
return t.foo;
}
};
template<typename T>
struct GetFoo<T, false>
{
static int value(T&)
{
return 0;
}
};
//-----------------------------------------------------

template<typename T>
int get_foo(T& t)
{
return GetFoo<T, has_foo<T>(0)>::value(t);
}
//-----------------------------------------------------

struct Bar
{
int val;
};
struct Foo {
int foo;
};

int main()
{
Bar b { 5 };
Foo f { 5 };
std::cout << get_foo(b) << std::endl;
std::cout << get_foo(f) << std::endl;
return 0;
}

SFINAE - detect if type T is pointer, array or container with random access operator and for given value type

How to combine both SFINAE conditions checking for array & pointer and random access operator into one check? I

The simplest way that come in my mind is check if you can write c[0u]

template <typename T>
auto DoIt(T& c) -> decltype( c[0u], void() )
{}

Not a perfect solution: works with types accepting an unsigned integer for as argument for operator[] (std::vectors, std::arrays, C-style arrays, pointers, std::maps with an integer key) but fails with maps with keys incompatibles with unsigned integers.

You can reduce this problem adding a template parameter for the key type (defaulting it to std::size_t)

template <typename K = std::size_t, typename T>
auto DoIt(T& c) -> decltype( c[std::declval<K>()], void() )
{}

so works as follows

std::array<int,6> c;

DoIt(c); // Ok, compile pass, no needs to explicit the key type

std::map<std::string, int> m;

DoIt(m); // compilation error: std::size_t is a wrong key type
DoIt<std::string>(m); // Ok, compile: std::string is a good key type

If you want enable the function checking also the type returned by the operator []... well... conceptually is simple but require a little typewriting

I propose the following DoIt2() function where you have to explicit the required type for operator [] and std::size_t remain the default type for the argument of the operator (but you can explicit a different type)

template <typename V, typename K = std::size_t, typename T>
std::enable_if_t<
std::is_same_v<V,
std::remove_const_t<
std::remove_reference_t<
decltype(std::declval<T>()[std::declval<K>()])>>>>
DoIt2 (T &)
{}

The idea is simple: get the type of std::declval<T>()[std::declval<K>()], remove reference (if present), remove const (if present) and check if the resulting type is equal to V (the requested type)

You can use DoIt2() as follows

std::vector<int>   v1;
std::vector<float> v2;

DoIt2<int>(v1); // compile
//DoIt2<int>(v2); // compilation error

//DoIt2<float>(v1); // compilation error
DoIt2<float>(v2); // compile

std::map<int, std::string> m1;
std::map<std::string, float> m2;

DoIt2<std::string, int>(m1);
DoIt2<float, std::string>(m2);

how to fix SFINAE check for operator== existing, so that it works with std::pair

As kenash0625 correctly noted, you need a specialization for std::pair to manually implement checks that std::pair itself does not do. However, at this point, I suggest simplifying your implementation with C++17 and without macros:

template<
template<typename...> typename Detector,
typename T,
typename SFINAE = void>
constexpr inline bool is_detected_v = false;

template<
template<typename...> typename Detector,
typename T>
constexpr inline bool is_detected_v<
Detector, T, std::void_t<Detector<T>>> = true;

We can now implement has_eq like this:

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

static_assert(is_detected_v<has_eq_t, std::pair<int, int>>);

We can also write a helper, whose first purpose is to increase code expressiveness:

template<typename T>
constexpr inline bool has_eq_v = is_detected_v<has_eq_t, T>;

static_assert( ! has_eq_v<SimpleClassWithoutComparisonOperators>);

Although the original issue has not been fixed yet, the second purpose of this helper is where we can implement some ad hoc checks for std::pair. Let's add the specialization:

template<typename T, typename U>
constexpr inline bool has_eq_v<std::pair<T, U>> = has_eq_v<T> && has_eq_v<U>;

Here we can add more and more specializations for such specific cases:

template<typename... Ts>
constexpr inline bool has_eq_v<std::tuple<Ts...>> = (has_eq_v<Ts> && ...);

Check the code now:

static_assert ( ! has_eq_v<PAIR_>);

Further reading:

  • You should read about the Detection Idioms, this is a more flexible implementation of my is_detected_v
  • C++20 concepts allow us to make our implementation way simpler. I think the concepts are the main reason why we don't have the std::is_detected in standard library. It just isn't needed with concepts.

SFINAE Based traits to determine if operator + is supported

Try with

template <typename T1, typename T2>
struct HasPlusT
{
private:
template <typename T>
using Rr = typename std::remove_reference<T>::type;

template<typename U1, typename U2>
static auto test(void *)
-> decltype(std::declval<Rr<U1>>() + std::declval<Rr<U2>>() , '0');

template<typename...>
static long test(...);

public:
static constexpr bool value
= IsSameT<decltype(test<T1,T2>(nullptr)),char>::value;
};

I mean... your code has a couple of problems.

In no particular order.

1 - If you call test<T1,T2>(nullptr), you explicitly pass two template types; so if you define test with a second type for the second parameter

template<typename U1, typename = decltype(T1_t() + T2_t())>
static char test(void *);

the second one is never used.

2 - SFINAE works with template parameter of the function; not with template parameter of the class. So if you try something as

template<typename U1, typename U2, typename = decltype(T1_t() + T2_t())>
static char test(void *);

SFINAE doesn't works because you're not using U1 and U2 (template parameter of the method) but with T1_t() and T2_t(), so T1 and T2, template parameters of the class.

So I suggest the use of a using Rr to remove reference

      template <typename T>
using Rr = typename std::remove_reference<T>::type;

and, to make simpler, to use SFINAE through returned type

  template<typename U1, typename U2>
static auto test(void *)
-> decltype(std::declval<Rr<U1>>() + std::declval<Rr<U2>>() , '0');

-- EDIT --

The OP can't use std::declval().

I use mingw and for an unknown reason cannot recognize declval

So I propose a better-than-nothing trivial substitute (without std::add_rvalue_reference<T>::type, in case you can't use it)

template <typename T>
T declVal();

and HasPlusT become

template <typename T1, typename T2>
struct HasPlusT
{
private:

template <typename T>
using Rr = typename std::remove_reference<T>::type;

template<typename U1, typename U2>
static auto test(void *)
-> decltype(declVal<Rr<U1>>() + declVal<Rr<U2>>() , '0');

template<typename...>
static long test(...);

public:
static constexpr bool value
= IsSameT<decltype(test<T1,T2>(nullptr)),char>::value;
};

-- EDIT 2 --

The OP say

what modifications are needed to support check for class templates? I try to do this for a class A but seems not to work. See in the initial post.

Isn't a problem of class templates.

The problem is that your operator+() for A classes

friend A<T> operator + (A<T> & a1, A<T> & a2)
{ return a1.add(a2); }

is unusual because is (correctly) a friend function of the class but receive references (left-references), not constant, to A<T> objects (usually are const references; the first value can be a value not referenced), modify the first received argument (dangerous) and return it by copy.

So the HasPlusT class fail because std::declval() return a r-value referenced object that doesn't match an operator+ that ask for l-values.

I strongly suggest you to modify operator+() as follows (a1 and a2 const)

  friend A<T> operator+ (A<T> const & a1, A<T> const & a2)
{ A<T> ret{a1}; return ret.add(a2); }

or, maybe better, as follows (a1 without &, a2 const)

  friend A<T> operator+ (A<T> a1, A<T> const & a2)
{ return a1.add(a2); }

and you'll see that HasPlusT works again.

C++ SFINAE with decltype: substitution failure becomes an error?

The thing is you're trying to extend from std::enable_if, but the expression you put inside the enable if may be invalid. Since you are using a class that inherit form that, the class you instanciate inherit from an invalid expression, hence the error.

An easy solution for having a name for your enable_if expression would be to use an alias instead of a class:

template <typename Iter, typename Target>
using is_iter_of = enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value>;

SFINAE will still work as expected with an alias.

This is because instancing an alias is part of the function you try to apply SFINAE on. With inheritance, the expression is part of the class being instanciated, not the function. This is why you got a hard error.

The thing is, the is multiple ways SFINAE is applied in your case. Let's take a look at where SFINAE can happen:

enable_if< //             here -------v
is_constructible<Target, decltype(*(declval<Iter>()))>::value
>::type
// ^--- here

Indeed, SFINAE will happen because enable_if::type will not exist if the bool parameter is false, causing SFINAE.

But if you look closely, another type might not exist: decltype(*(std::declval<Iter>())). If Iter is int, asking for the type of the star operator makes no sense. So SFINAE if applied there too.

Your solution with inheritance would have work if every class you send as Iter had the * operator available. Since with int it does not exist, you are sending a non existing type to std::is_constructible, making the whole expression forming the base class invalid.

With the alias, the whole expression of using std::enable_if is subject to apply SFINAE. Whereas the base class approach will only apply SFINAE on the result of std::enable_if.

Using SFINAE to detect method with GCC

In the event someone else encounters a similar error/misunderstanding, my error (aptly pointed out by n. 'pronouns' m.) was using the wrong type in the has_serialize::test. From what I can infer (in my naivety) is that for

template<typename T>
struct has_serialize
{
// ...
template<typename C>
static constexpr auto test(int)
-> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());
// ...
};

the template C does not appear in has_serialize::test, and as a result GCC does not provide an opportunity for substitution failure. Consequently, GCC tries to evaluate std::declval<T>().serialize(...) and obviously throws an error when T = std::vector<double>. If T is replaced with C in this line, then the compiler recognizes it as a substitution failure and determines the signature to be unsuitable.

Furthermore, as Maxim Egorushkin pointed out, there is the possibility that T::serialize might return a user-defined type that overloads operator,. To ameliorate (albeit very unlikely) potential error, the code should be:

template<typename T>
struct has_serialize
{
// ...
template<typename C>
static constexpr auto test(int)
-> decltype(static_cast<void>(std::declval<C>().serialize(std::declval<SerializerBase&>())), std::true_type());
// ...
};


Related Topics



Leave a reply



Submit