Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;

private:
  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));
  };

public:
  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 AId<> have operator++ only if AId<>::value_type 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.

I'm now using @Barry's answear which is both simple and correct.

like image 687
Olivetree Avatar asked Oct 12 '16 12:10

Olivetree


2 Answers

I see no need for SFINAE, or no need for anything at all.

Just implement your operator++. If the underlying class does not support it, and operator++ is not invoked for your template wrapper, the operator does not get instantiated, no harm no foul.

Tested with gcc 6.2.1, note that mytemplate<not_incrementable> will instantiate with no issues, as long as nothing tries to increment it:

#include <iostream>
#include <vector>

class not_incrementable {};

class incrementable {
public:

    incrementable &operator++()
    {
        return *this;
    }
};

template<typename T> class mytemplate {

public:

    T t;

    mytemplate<T> operator++()
    {
        ++t;
        return *this;
    }
};

void foo()
{
    mytemplate<not_incrementable> will_this_compile;

    mytemplate<incrementable> will_this_compile2;

    ++will_this_compile2; // Compiles

    // ++will_this_compile; // Will not compile
}
like image 129
Sam Varshavchik Avatar answered Oct 15 '22 16:10

Sam Varshavchik


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
template<typename...>
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);
        ++(self().id);
        return old;
    }

private:
    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.

like image 20
Guillaume Racicot Avatar answered Oct 15 '22 14:10

Guillaume Racicot