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.
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:
The type
void_t
can be implemented like this (with C++11 compatibility):Now, we can implement the
operator++
of your class in a mixin:Now, to conditionally implement the
operator++
function, you can usestd::conditional
with our trait:Now, since you extends the mixin only if the type is matching the type trait, you only get the
operator++
ifTID
has the increment operator. You end up extendingDummy
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.
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, andoperator++
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: