SFINAE只有在可能的情况下才有班级成员

时间:2016-10-12 12:36:35

标签: c++ c++11 templates sfinae

我已经创建了一个类型安全的ID类,但是现在我想支持operator ++,如果底层类型也有它。从thisthis回答我已经提出了两个替代方案,但是当它们用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<>如果operator++也有AId<>::value_type section { height: 100%; width: 100%; display:inline-block; background:red; } *{ padding:0px; margin:0px; }怎么办?我只限于c ++ 11而且没有提升。

之前还有第二个问题,我自己提出了一个问题here

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

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

2 个答案:

答案 0 :(得分:7)

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

只需实施operator++即可。如果底层类不支持它,并且没有为您的模板包装器调用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
}

答案 1 :(得分:4)

正如其他答案中所述,实际上,如果你不使用它们,你可以留下没有实例化错误的函数。

但是,如果有人使用sfinae尝试检查你的班级是否支持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中实现您班级的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,所以如果operator++具有增量运算符,则只获得TID。如果没有,您最终会延长Dummy,而不会实施运营商。

如果您想要有条件地实现复制和移动构造函数,那么这个技巧很不错。