在不触及主模板的情况下使用 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';
}