不改动主模板使用SFINAE部分特化

9

我正在尝试使用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,因为它是外部头文件库的一部分。因此,主模板将需要像这样:

template <typename T>
struct S {
    void operator()() {
        std::cout << "Instantiated generic case" << std::endl;
    }
};

有没有办法我仍然可以使用SFINAE来专门化此模板?

编辑:请注意,外部库中的代码使用S结构体,因此我必须实际专门化S,而不能对其进行子类化。 此外,我正在处理的真实代码要比这个简单示例复杂得多,并且将从SFINAE中受益良多(我有多个模板参数需要针对多种类型组合进行专门化)。


根据您感兴趣的实际T表单类型,这可能会有所帮助:https://dev59.com/Fmcs5IYBdhLWcg3wcDeZ#30991097 - bogdan
3个回答

4
我有一个不好的消息要告诉你:你想要的是不可能实现的。如果主模板没有一个额外的模板参数,用于执行enable_if,并且默认为void,那么在最普遍的情况下就没有办法实现这一点。你能做到的最接近的方法是为一个模板类本身专门化结构体,换句话说:
template <class T>
struct foo {};

template <class T>
struct S<foo<T>> {};

这将有效,但显然这不会像针对匹配特征的东西进行专门化那样灵活。
您的问题实际上与尝试为满足特征的任何类型专门化 std :: hash 的问题完全相同。与您的问题一样,主类模板定义不能更改,因为它在库代码中,而库代码实际上在某些情况下自动内部使用哈希的专业化,因此不能通过定义新的模板类来解决。
您可以在此处查看类似的问题:Specializing std::hash to derived classes。从类继承是可表达为特征但无法作为模板类的好例子。一些非常有见地的 C++ 人士关注了那个问题,没有人提供比 OP 解决方案更好的答案。我认为,对于您来说,这将是最佳解决方案,不幸的是。
回想起来,也许应该声明第二个模板参数为默认值 void 以支持使用最小开销的用例。我甚至能发誓我在某个地方看到过这方面的讨论,但我找不到跟踪信息。对于您的库代码,您可以尝试提交问题以使其更改。它似乎不是破坏性的更改,也就是说:
template <class T, class = void>
struct S {};

template <>
struct S<double> {};

看起来是有效的。


非常感谢您的解释。对于我的特定问题,宏似乎是解决方案,但使用特质进行专业化的有趣一般性问题似乎没有解决方案。 - ibab
foo是否可以通过SFINAE定义,以便仅在foo满足自己的SFINAE条件时才定义S<foo<T>>的特化? - xaviersjs

4
在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';
}

2
你确定普通的专业化不够吗?这是否足够?
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而存在的额外参数,那将会更好,但仍不是指数级别的...


根据更新的问题(和我的初步怀疑),float_or_int 只是一个例子。实际的重点是为满足特定 trait 的所有类型专门化结构体,因此这并没有回答问题。 - Nir Friedman
2
是的,int/float 专门化只是一个例子。我开始考虑 SFINAE 是因为我意识到它会简化代码,超出了我可以通过使用宏实现的范围。 在我的情况下,我有多个需要专门化的模板参数,这意味着有一些组合爆炸。 - ibab
使用单独的结构体并从中继承进行特化可以减少需要编写的大量代码。使用这种方法,宏解决方案看起来并不太糟糕。非常感谢! - ibab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接