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
,这没有实现运算符。
如果您想有条件地实现复制和移动构造函数,这个技巧非常有用。
我已经创建了一个类型安全的 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
,这没有实现运算符。
如果您想有条件地实现复制和移动构造函数,这个技巧非常有用。