SFINAE 仅在可能的情况下拥有 class 成员

SFINAE to have a class member only if possible

我已经创建了一个类型安全的 ID class,但现在我想支持 operator++,如果基础类型也有的话。从 this and this answears 我想出了 2 个替代方案,但是当用 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; }
};

只有AId<>::value_type也有,AId<>怎么可能有operator++呢?我仅限于 c++11,没有提升。

之前有第二个问题,我自己做了一个问题

虽然我实际上在我的代码中使用了@Sam Varshavchik answear,但我认为@Guillaume Racicot 提供的那个更通用,所以我选择了它作为解决方案。

我现在正在使用@Barry 的 ,它既简单又正确。

我认为不需要 SFINAE,或者根本不需要任何东西。

只需实施您的 operator++。如果底层 class 不支持它,并且没有为您的模板包装器调用 operator++,则运算符不会被实例化,没有坏处。

已使用 gcc 6.2.1 进行测试,请注意 mytemplate<not_incrementable> 将在没有任何问题的情况下实例化,只要没有任何尝试增加它:

#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
}

正如其他答案中所述,确实,如果您不使用有错误的函数,则可以不实例化它们。

但是,如果有人使用 sfinae 来尝试检查您的 class 是否支持 operator++,他的类型特征将给他误报,从而导致潜在的编译错误。如果你想支持那个用例,你没有太多选择。您需要有条件地执行它。

如果你想有条件地实现一个成员,你可以使用继承。

我们将把 sfinae 放在该特征中并在之后使用该特征:

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 {};

类型void_t可以这样实现(兼容C++11):

// 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;

现在,我们可以在 mixin 中实现 class 的 operator++

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

现在,要有条件地实现 operator++ 函数,您可以将 std::conditional 与我们的特征一起使用:

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 */
};

现在,由于仅当类型与类型特征匹配时才扩展 mixin,因此如果 TID 具有增量运算符,则只会得到 operator++。如果没有,您最终会扩展 Dummy,这没有实现运算符。

如果您想有条件地实现复制和移动构造函数,这个技巧非常有用。