将枚举值与 SFINAE 结合使用
Using enum values in combination with SFINAE
我已经熟悉 SFINAE 以及如何使用它根据传递的类型启用特定模板(通过使用 std::enable_if)。但是,我最近开始从事一个项目,我想在其中执行以下操作:在使用 SFINAE 时根据提供的枚举值创建一个 class 专业化。现在,我知道可以根据枚举值进行专业化,因为我之前已经这样做过(像这样):
enum Specifier
{
One,
Two,
Three
}
template <Specifier>
class Foo
{
public:
void Bar();
}
template<>
void Foo<Specifier::One>::Bar()
{
}
但是现在我想使用 SFINAE 对多个枚举值使用 Bar()
的特定专业化。像这样:
template <Specifier Type>
class Foo
{
public:
template <typename std::enable_if<Type == Specifier::Two || Type == Specifier::One, void>::type>
void Bar();
template <typename std::enable_if<Type == Specifier::Three, void>::type>
void Bar();
}
知道这是否可行吗?如果可行,我该怎么做?
C++17: constexpr if
从 C++17 及更高版本开始,您可以使用单个成员函数重载(而不是通过 SFINAE 存在或不存在的多个重载),其主体利用 constexpr 如果:
#include <iostream>
enum class Specifier { One, Two, Three };
template <Specifier S> class Foo {
public:
static constexpr int bar() {
if constexpr ((S == Specifier::One) || (S == Specifier::Two)) {
return 12;
} else if constexpr (S == Specifier::Three) {
return 3;
}
}
};
int main() {
std::cout << Foo<Specifier::One>::bar() << "\n" // 12
<< Foo<Specifier::Two>::bar() << "\n" // 12
<< Foo<Specifier::Three>::bar(); // 3
}
C++11:SFINAE 和 std::enable_if
(_t
) (C++14)
您同样可以使用 SFINAE,但要求您的非模板成员函数需要成为具有虚拟模板参数的成员函数模板,因为 SFINAE 需要应用于 从属名称 在每个函数声明中,并且 class 模板(类型或非类型)参数自然不是 非模板 成员函数声明中的从属名称:
template <Specifier S> class Foo {
public:
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::One) || (S_ == Specifier::Two)>
* = nullptr>
static constexpr int bar() {
return 12;
}
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::Three)> * = nullptr>
static constexpr int bar() {
return 3;
}
};
请注意,上面的示例使用了 C++14 中引入的助手别名模板 std::enable_if_t
。如果您使用的是 C++11,则需要改用 typename std::enable_if<..>::type
。
此外请注意,由于我们必须将成员函数模板化,滥用用户可以选择覆盖(虚拟)非类型模板参数的默认模板参数 S_
:
Foo<Specifier::One>::bar<Specifier::Three>(); // 3
因此我们可能希望为每个重载的std::enable_if_t
谓词添加一个额外的AND 条件,即(S_ == S) && (... predicate as above)
。正如我们将在下一节中看到的那样,这在 C++20 中不再是一个问题,因为我们可以避免将非模板成员函数制作成模板,仅用于应用 SFINAE。
使用特化而不是重载的替代方法
正如我在 中针对此问题的后续问题所示,您还可以在模板参数列表中应用 SFINAE(对 class 模板进行部分特化)专业:
template <Specifier, typename = void> struct Foo {
static constexpr int bar() { return 1; } // default
};
template <Specifier S>
struct Foo<S,
std::enable_if_t<(S == Specifier::One) || (S == Specifier::Two)>> {
static constexpr int bar() { return 12; }
};
C++20: class 模板的非模板成员函数可以使用 requires-clause:s
从 C++20 开始,您可以使用具有互斥性的尾随 requires-clause 来重载和约束 class 模板的非模板成员函数每个重载的约束:
template <Specifier S> class Foo {
public:
static constexpr int bar() requires((S == Specifier::One) ||
(S == Specifier::Two)) {
return 12;
}
static constexpr int bar() requires(S == Specifier::Three) { return 3; }
};
我已经熟悉 SFINAE 以及如何使用它根据传递的类型启用特定模板(通过使用 std::enable_if)。但是,我最近开始从事一个项目,我想在其中执行以下操作:在使用 SFINAE 时根据提供的枚举值创建一个 class 专业化。现在,我知道可以根据枚举值进行专业化,因为我之前已经这样做过(像这样):
enum Specifier
{
One,
Two,
Three
}
template <Specifier>
class Foo
{
public:
void Bar();
}
template<>
void Foo<Specifier::One>::Bar()
{
}
但是现在我想使用 SFINAE 对多个枚举值使用 Bar()
的特定专业化。像这样:
template <Specifier Type>
class Foo
{
public:
template <typename std::enable_if<Type == Specifier::Two || Type == Specifier::One, void>::type>
void Bar();
template <typename std::enable_if<Type == Specifier::Three, void>::type>
void Bar();
}
知道这是否可行吗?如果可行,我该怎么做?
C++17: constexpr if
从 C++17 及更高版本开始,您可以使用单个成员函数重载(而不是通过 SFINAE 存在或不存在的多个重载),其主体利用 constexpr 如果:
#include <iostream>
enum class Specifier { One, Two, Three };
template <Specifier S> class Foo {
public:
static constexpr int bar() {
if constexpr ((S == Specifier::One) || (S == Specifier::Two)) {
return 12;
} else if constexpr (S == Specifier::Three) {
return 3;
}
}
};
int main() {
std::cout << Foo<Specifier::One>::bar() << "\n" // 12
<< Foo<Specifier::Two>::bar() << "\n" // 12
<< Foo<Specifier::Three>::bar(); // 3
}
C++11:SFINAE 和 std::enable_if
(_t
) (C++14)
您同样可以使用 SFINAE,但要求您的非模板成员函数需要成为具有虚拟模板参数的成员函数模板,因为 SFINAE 需要应用于 从属名称 在每个函数声明中,并且 class 模板(类型或非类型)参数自然不是 非模板 成员函数声明中的从属名称:
template <Specifier S> class Foo {
public:
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::One) || (S_ == Specifier::Two)>
* = nullptr>
static constexpr int bar() {
return 12;
}
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::Three)> * = nullptr>
static constexpr int bar() {
return 3;
}
};
请注意,上面的示例使用了 C++14 中引入的助手别名模板 std::enable_if_t
。如果您使用的是 C++11,则需要改用 typename std::enable_if<..>::type
。
此外请注意,由于我们必须将成员函数模板化,滥用用户可以选择覆盖(虚拟)非类型模板参数的默认模板参数 S_
:
Foo<Specifier::One>::bar<Specifier::Three>(); // 3
因此我们可能希望为每个重载的std::enable_if_t
谓词添加一个额外的AND 条件,即(S_ == S) && (... predicate as above)
。正如我们将在下一节中看到的那样,这在 C++20 中不再是一个问题,因为我们可以避免将非模板成员函数制作成模板,仅用于应用 SFINAE。
使用特化而不是重载的替代方法
正如我在
template <Specifier, typename = void> struct Foo {
static constexpr int bar() { return 1; } // default
};
template <Specifier S>
struct Foo<S,
std::enable_if_t<(S == Specifier::One) || (S == Specifier::Two)>> {
static constexpr int bar() { return 12; }
};
C++20: class 模板的非模板成员函数可以使用 requires-clause:s
从 C++20 开始,您可以使用具有互斥性的尾随 requires-clause 来重载和约束 class 模板的非模板成员函数每个重载的约束:
template <Specifier S> class Foo {
public:
static constexpr int bar() requires((S == Specifier::One) ||
(S == Specifier::Two)) {
return 12;
}
static constexpr int bar() requires(S == Specifier::Three) { return 3; }
};