在不触及主模板的情况下使用 SFINAE 部分专业化
Using SFINAE partial specialization without touching the primary template
我正在尝试使用一次为多种类型专门化一个结构模板
SFINAE。我知道像下面这样的东西有效:
#include <iostream>
template <typename T, typename Enable = void>
struct S {
void operator()() {
std::cout << "Instantiated generic case" << std::endl;
}
};
template<typename T>
using enabled_type = typename std::enable_if<
std::is_same<T, int>::value ||
std::is_same<T, float>::value
>::type;
template <typename T>
struct S<T, enabled_type<T>> {
void operator()() {
std::cout << "Instantiated int/float case" << std::endl;
}
};
int main() {
S<float>()();
return 0;
}
我的问题是我无法修改 S
的主模板
添加 typename Enable = void
的结构,因为它是外部 header-only 的一部分
图书馆。所以主模板必须如下所示:
template <typename T>
struct S {
void operator()() {
std::cout << "Instantiated generic case" << std::endl;
}
};
有没有办法让我仍然可以使用 SFINAE 来专门化这个模板?
编辑: 请注意,S
结构由外部库中的代码使用,因此我必须实际专门化 S
并且不能'不要将它子类化。
此外,我正在处理的实际代码要复杂得多,并且会比这个简单的示例从 SFINAE 中受益更多(我有多个模板参数需要针对多种类型的所有组合进行专门化)。
您确定普通的旧专业化还不够吗?
struct S_int_or_float {
void operator()() {
std::cout << "Instantiated int/float case" << std::endl;
}
};
template<> struct S<int>: S_int_or_float {};
template<> struct S<float>: S_int_or_float {};
编辑:
你说你必须提供参数的组合......所以至少有两个......让我们看看我们可以用它做什么:
struct ParentS { /* some implementation */ };
template <class First, class Second>
struct S<First, Second, enable_if_t<trait_for_first_and_second<First, Second>::value, first_accepted_type_of_third_parameter> >: ParentS { };
template <class First, class Second>
struct S<First, Second, enable_if_t<trait_for_first_and_second<First, Second>::value, second_accepted_type_of_third_parameter> >: ParentS { };
// ...
它不如 SFINAE 有额外的参数但仍然不是指数...
我有个坏消息要告诉你:你想要的是不可能的。如果主模板没有为了执行 enable_if
而默认为 void
的额外模板参数,那么在最一般的意义上就没有办法做到这一点。最接近的是为模板 class 本身专门化结构,换句话说:
template <class T>
struct foo {};
template <class T>
struct S<foo<T>> {};
这行得通。但显然这不会产生与专门化某些东西相同的灵活性,前提是它符合特征。
您的问题实际上完全等同于尝试将 std::hash
专门化为满足特征的任何类型的问题。就像在您的问题中一样,主要的 class 模板定义无法更改,因为它在库代码中,并且库代码实际上在某些情况下会在内部自动使用 hash
的特化,因此不能真正定义一个新模板 class.
您可以在这里看到类似的问题:Specializing std::hash to derived classes。从 class 继承是一个很好的例子,它可以表示为特征,但不能表示为模板 class。一些知识渊博的 C++ 人士关注了这个问题,没有人提供比 OP 编写宏以自动消除专业化的解决方案更好的答案。不幸的是,我认为这对您来说也是最好的解决方案。
回想起来,hash
或许应该使用第二个模板参数声明,该参数默认为 void 以在其他情况下以最小的开销支持此用例。我本可以发誓我什至在某个地方看到过关于这个的讨论,但我无法找到它。对于您的库代码,您可能会尝试提交问题以让他们更改它。好像不是breaking change,就是:
template <class T, class = void>
struct S {};
template <>
struct S<double> {};
似乎有效。
在 C++20 中,您可以使用概念来限定模板参数。
在 g++ 11.2 和 clang 13.0 上,以下内容按预期工作。
#include <iostream>
template <typename T> concept isok = std::is_floating_point_v<T>;
template <typename T> struct ob { T field; ob(T t) : field( t) { } };
template <isok T> struct ob <T> { T field; ob(T t) : field(3*t) { } };
// Prints 9 9 27 27
int main() {
std::cout << ob(9).field << ' ' << ob('9').field << ' '
<< ob(9.0).field << ' ' << ob(9.0f).field << '\n';
}
我正在尝试使用一次为多种类型专门化一个结构模板 SFINAE。我知道像下面这样的东西有效:
#include <iostream>
template <typename T, typename Enable = void>
struct S {
void operator()() {
std::cout << "Instantiated generic case" << std::endl;
}
};
template<typename T>
using enabled_type = typename std::enable_if<
std::is_same<T, int>::value ||
std::is_same<T, float>::value
>::type;
template <typename T>
struct S<T, enabled_type<T>> {
void operator()() {
std::cout << "Instantiated int/float case" << std::endl;
}
};
int main() {
S<float>()();
return 0;
}
我的问题是我无法修改 S
的主模板
添加 typename Enable = void
的结构,因为它是外部 header-only 的一部分
图书馆。所以主模板必须如下所示:
template <typename T>
struct S {
void operator()() {
std::cout << "Instantiated generic case" << std::endl;
}
};
有没有办法让我仍然可以使用 SFINAE 来专门化这个模板?
编辑: 请注意,S
结构由外部库中的代码使用,因此我必须实际专门化 S
并且不能'不要将它子类化。
此外,我正在处理的实际代码要复杂得多,并且会比这个简单的示例从 SFINAE 中受益更多(我有多个模板参数需要针对多种类型的所有组合进行专门化)。
您确定普通的旧专业化还不够吗?
struct S_int_or_float {
void operator()() {
std::cout << "Instantiated int/float case" << std::endl;
}
};
template<> struct S<int>: S_int_or_float {};
template<> struct S<float>: S_int_or_float {};
编辑: 你说你必须提供参数的组合......所以至少有两个......让我们看看我们可以用它做什么:
struct ParentS { /* some implementation */ };
template <class First, class Second>
struct S<First, Second, enable_if_t<trait_for_first_and_second<First, Second>::value, first_accepted_type_of_third_parameter> >: ParentS { };
template <class First, class Second>
struct S<First, Second, enable_if_t<trait_for_first_and_second<First, Second>::value, second_accepted_type_of_third_parameter> >: ParentS { };
// ...
它不如 SFINAE 有额外的参数但仍然不是指数...
我有个坏消息要告诉你:你想要的是不可能的。如果主模板没有为了执行 enable_if
而默认为 void
的额外模板参数,那么在最一般的意义上就没有办法做到这一点。最接近的是为模板 class 本身专门化结构,换句话说:
template <class T>
struct foo {};
template <class T>
struct S<foo<T>> {};
这行得通。但显然这不会产生与专门化某些东西相同的灵活性,前提是它符合特征。
您的问题实际上完全等同于尝试将 std::hash
专门化为满足特征的任何类型的问题。就像在您的问题中一样,主要的 class 模板定义无法更改,因为它在库代码中,并且库代码实际上在某些情况下会在内部自动使用 hash
的特化,因此不能真正定义一个新模板 class.
您可以在这里看到类似的问题:Specializing std::hash to derived classes。从 class 继承是一个很好的例子,它可以表示为特征,但不能表示为模板 class。一些知识渊博的 C++ 人士关注了这个问题,没有人提供比 OP 编写宏以自动消除专业化的解决方案更好的答案。不幸的是,我认为这对您来说也是最好的解决方案。
回想起来,hash
或许应该使用第二个模板参数声明,该参数默认为 void 以在其他情况下以最小的开销支持此用例。我本可以发誓我什至在某个地方看到过关于这个的讨论,但我无法找到它。对于您的库代码,您可能会尝试提交问题以让他们更改它。好像不是breaking change,就是:
template <class T, class = void>
struct S {};
template <>
struct S<double> {};
似乎有效。
在 C++20 中,您可以使用概念来限定模板参数。
在 g++ 11.2 和 clang 13.0 上,以下内容按预期工作。
#include <iostream>
template <typename T> concept isok = std::is_floating_point_v<T>;
template <typename T> struct ob { T field; ob(T t) : field( t) { } };
template <isok T> struct ob <T> { T field; ob(T t) : field(3*t) { } };
// Prints 9 9 27 27
int main() {
std::cout << ob(9).field << ' ' << ob('9').field << ' '
<< ob(9.0).field << ' ' << ob(9.0f).field << '\n';
}