Olivetree Olivetree - 1 year ago 64
C++ Question

SFINAE to have a class member only if possible

I have made a type-safe ID class, but now I want to support operator++ if the underlying type also has it. From this and this answears I have come up with 2 alternatives, but they both fail when instantiated with AId:

template<typename T, typename TID = unsigned int>
struct AId {
typedef AId<T, TID> type;
typedef T handled_type;
typedef TID value_type;

value_type id;

template<typename _T> struct IsIncrementable
template<typename _U> using rm_ref = typename std::remove_reference<_U>::type;
typedef char (&yes)[1];
typedef char (&no)[2];
template<class _U>
static yes test(_U *data, typename std::enable_if<
std::is_same<_U, rm_ref<decltype(++(*data))>>::value
>::type * = 0);
static no test(...);
static const bool value = sizeof(yes) == sizeof(test((rm_ref<_T> *)0));

explicit AId(const value_type &id) : id(id) {}


//This fails with error: no match for 'operator++' (operand type is
//'AId<some_type, std::basic_string<char> >::value_type
//{aka std::basic_string<char>}')
//auto operator++() -> decltype(++id, std::ref(type())) { ++id; return *this; }
// ^
template<typename = decltype(++id)>
auto operator++() -> decltype(++id, std::ref(type())) { ++id; return *this; }

//error: no type named 'type' in 'struct std::enable_if<false, int>'
template<typename std::enable_if<IsIncrementable<value_type>::value, int>::type = 0>
type operator++(int /*postfix*/) { type old(id); ++id; return old; }

How can
only if
also has it? I'm limited to c++11 and no boost.

Previously there was a second question from which I have made a question on its own here.

Although I'm actually using @Sam Varshavchik answear in my code, I consider the one provided by @Guillaume Racicot to be more general, so I chose that as a solution.

Answer Source

As stated in other answer, indeed, you can left function that have errors not instantiated if you don't use them.

However, if someone use sfinae to try to check if your class supports operator++, his type trait will give him false positive, causing potential compilation errors. If you want to support that use case, you are left with not a lot of choices. You need to implement it conditionally.

If you want conditional implementation of a member, you can use inheritance.

We will put sfinae in that trait and use that trait after:

template<typename, typename>
struct has_increment : std::false_type {};

template<typename T>
struct has_increment<T, void_t<decltype(++std::declval<T>())>> : std::true_type {};

The type void_t can be implemented like this (with C++11 compatibility):

// void_t implemented with a struct works better for c++11 compilers
struct voider { using type = void; };

template<typename... Ts>
using void_t = typename voider<Ts...>::type;

Now, we can implement the operator++ of your class in a mixin:

template<typename Child>
struct MixinIncrement {
    auto operator++(int /*postfix*/) {
        Child::type old(self().id);
        return old;

    const Child& self() const { return *static_cast<const Child*>(this); }
    Child& self() { return *static_cast<Child*>(this); }

Now, to conditionally implement the operator++ function, you can use std::conditional with our trait:

struct Dummy {};

template<typename Child>
using Parent = typename std::conditional<has_increment<TID>::value, MixinIncrement<Child>, Dummy>::type;

template<typename T, typename TID = unsigned int>
struct AId : Parent<AId<T, TID>> {
    /* your stuff */

Now, since you extends the mixin only if the type is matching the type trait, you only get the operator++ if TID has the increment operator. You end up extending Dummy if not, which don't have the operator implemented.

This very same trick is nice if you want to conditionally implement copy and move constructors.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download