C++函数模板部分特化?

kyks70gy  于 2023-03-25  发布在  其他
关注(0)|答案(7)|浏览(187)

我知道下面的代码是一个类的部分特殊化:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 

// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
};

我也知道C++不允许函数模板部分专用化(只允许完全专用化)。但是我的代码是否意味着我已经为一个/相同类型的参数部分专用化了我的函数模板?因为它适用于Microsoft Visual Studio 2010速成版!如果不是,那么你能解释一下部分专用化的概念吗?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}

int main ()
{
    cout << max(4,4.2) << endl;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}
zpf6vheq

zpf6vheq1#

根据标准,函数部分特殊化不允许。在示例中,您实际上是重载而不是特殊化max<T1,T2>函数。
如果允许的话,它的语法应该看起来像下面这样:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{            //    ^^^^^ <--- supposed specializing here as an example
  return a; // can be anything of type T
}

在函数模板的情况下,C++标准只允许完全特殊化
有一些编译器扩展允许部分专门化,但代码在这种情况下失去了可移植性!

nfeuvbwi

nfeuvbwi2#

由于不允许部分专门化(正如其他答案所指出的那样),您可以使用std::is_samestd::enable_if来解决它,如下所示:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

输出:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

编辑:如果你需要处理所有剩下的case,你可以添加一个定义,声明已经处理过的case不应该 match --否则你会陷入模棱两可的定义。定义可以是:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

它产生:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

尽管这种“所有情况”的做法看起来有点无聊,但由于必须告诉编译器您已经完成的所有工作,因此处理多达5个或更多的专门化是完全可行的。

xpszyzbs

xpszyzbs3#

什么是专业化?
如果你真的想了解模板,你应该看看函数式语言。C++中的模板世界是它自己的一个纯函数式子语言。
在函数式语言中,选择是使用 Pattern Matching 完成的:

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

正如你所看到的,我们 * 重载 * 了isJust的定义。
C++类模板的工作方式完全相同。您提供一个 main 声明,说明参数的数量和性质。它可以只是一个声明,也可以作为一个定义(您的选择),然后您可以(如果您愿意)提供模式的专门化,并将其与类的不同版本相关联(否则将是愚蠢的)。
对于模板函数来说,特殊化有点尴尬:它与重载解析有些冲突。因此,已经决定专门化将与非专门化版本相关,并且在重载解析期间将不考虑专门化。因此,选择正确函数的算法变为:
1.在常规函数和非专用模板之间执行重载解析
1.如果选择了一个非专门化的模板,检查是否存在一个更好匹配的专门化
(for关于深入治疗,见GotW #49
因此,函数的模板特殊化是第二区公民(字面上)。就我而言,没有它们会更好:我还没有遇到过这样的情况,即模板专门化的使用不能用重载来解决。
这是模板特化吗?
不,它只是一个重载,这很好。事实上,重载通常像我们期望的那样工作,而专门化可能会令人惊讶(记得我链接的GotW文章)。

ubof19bj

ubof19bj4#

不允许非类、非变量的部分特化,但正如所说:
计算机科学中的所有问题都可以通过另一个层次的间接来解决。-大卫惠勒
添加一个类来转发函数调用可以解决这个问题,下面是一个例子:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");
k3bvogb1

k3bvogb15#

我很抱歉这么晚才回答,但我找到了一个解决方案,我没有看到其他答案中的解释(至少没有直接解释)。
一个函数不能 * 部分特化 *,而一个类可以。这里可以做的是在类中的静态函数。我们可以让它工作,基本上是将“模板部分特化”移动到类特化中,并在其中创建标记为静态的函数。这将允许我们通过增加一点所需的代码行来构造我们的部分特化函数。
让我们考虑不可用的部分专用函数Printer如下(代码根本无法编译)。

template <class T, class Trait = void>
void Printer(const T&);

template <class T>
void Printer<T, std::enable_if_t<std::is_floating_point_v<T>>>(const T& v){
    std::cout << "I m partially specialized for any floating point type." << std::endl;
}
template <class T>
void Printer<T, std::enable_if_t<std::is_integral_v<T>>>(const T& v){
    std::cout << "I m partially specialized for any integral type." << std::endl;
}

我们可以使用静态类函数并将部分特殊化移动到类上,而不是像这样:

namespace detail{
    
    template<class T, class Trait = void>
    struct Specialized;

    template<class T>
    struct Specialized<T, std::enable_if_t<std::is_floating_point_v<T>>>
    {
        static void Printer(const T& v){
            std::cout << "I m specialized for any floating point type"<< std::endl;
        }
    };

    template<class T>
    struct Specialized<T, std::enable_if_t<std::is_integral_v<T>>>
    {
        static void Printer(const T& v){
            std::cout << "I m specialized for any integral type"<< std::endl;
        }
    };
}

template<class T>
void Printer(const T& v)
{
    detail::Specialized<T>::Printer(v);   
}

它的结果有点长,但会解决我们的问题与一个相对明确的方式。你可以测试它在godbolt here
------编辑:感谢KROy的提示
它可以通过将两个静态函数 Package 在一个结构体中而使其更小,并在其上保留模板专门化:

namespace detail{
    struct Specialized{
        template<class T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
        static void Printer(const T& v){
            std::cout << "I'm specialized for integral types." << std::endl;
        }

        template<class T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
        static void Printer(const T& v){
            std::cout << "I'm specialized for floating point types." << std::endl;
        }
    };
}

template<class T>
void Printer(const T& v)
{
    detail::Specialized::Printer(v);   
}

它可以在godbolt here上测试。

nwlqm0z1

nwlqm0z16#

不能。例如,你可以合法地特殊化std::swap,但你不能合法地定义自己的重载。这意味着你不能让std::swap为你自己的自定义类模板工作。
重载和部分特化在某些情况下可以产生相同的效果,但远非所有情况。

col17t5w

col17t5w7#

迟来的答案,但一些迟来的读者可能会发现它很有用:有时候,一个辅助函数(设计成可以专门化)也可以解决这个问题。
所以让我们想象一下,这就是我们试图解决的问题:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

好的,部分模板函数专门化,我们不能这样做......所以让我们将专门化所需的部分“导出”到一个helper函数中,专门化并使用它:

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

这 * 可能 * 是有趣的,特别是如果替代方案(正常重载而不是专业化,Rubens提出的workaround,... -不是说这些不好或我的更好,只是 * 另一个 *)将共享相当多的公共代码。

相关问题