为什么模板特化不能改变返回类型?

4

阅读完这个问题后,我再次意识到自己对于模板的了解很少。我能够理解像这样的一个模板特化:

// A
template <typename T> void foo(T x){}
template <> void foo<double>(int x){}

无法工作 (错误:模板ID 'foo<double>'对于'void foo(int)'不匹配任何模板声明)。这不仅没有意义,而且参数推导也没有机会得到正确的T。然而,我不明白为什么它对于返回类型不起作用:

// B
template <typename T> int foo(T x){}  
template <> double foo<double>(double x){}

(与上述错误类似)。实际上,我没有任何特定的用例,但我仍然对如何根据T选择返回类型感兴趣。作为一种解决方法,我找到了这个:

// C 
template <typename T> struct foo { static int moo(T x){return x;} };
template <> struct foo<double> { static double moo(double x){return x;} };

因此,可以根据T选择返回类型。但是,我仍然感到困惑...

B不可能的原因是什么?


1
你的例子中实际上没有使用T作为返回类型,你期望什么?模板并不能完全打破语法规则。 - πάντα ῥεῖ
2
通过模板特化,您可以为函数模板提供另一种(专门的)实现;但无法更改其原型。 - Igor Tandetnik
1
作为其他解决方法,您可以采用返回类型特征,就像skypjack的示例中一样,或者在您的情况下,只需进行重载即可:double foo(double x); - Jarod42
我认为你在概念上是在寻找重载,而不是专门化。 - Johannes Schaub - litb
3个回答

4
即使看起来有些奇怪,你可能仍然需要
template <typename T>
void foo(int);

template <typename T>
char foo(int);

演示

所以你的专业领域会比较模糊。


编译器应该拒绝那段代码,并显示“你不能通过”的错误信息(半引用)。 :-) - skypjack
@skypjack 当函数模板具有完全相同的参数列表,但具有不同的std::enable_if_t返回类型,以便对于每个Tfoo<T>最多只能引用重载中的一个时,这实际上是一个有用的功能。 - user743382
@hvd 嗯,没错,我确实经常使用它。我只是开玩笑而已。 :-) - skypjack

3

实际上,您可以通过使用返回类型的模板参数或特性类来解决这个问题。
例如:

#include<type_traits>

template<typename T>
T f() { return T{}; }

template<typename>
struct Traits;

template<>
struct Traits<int> {
    using ReturnType = char;
};

template<>
struct Traits<char> {
    using ReturnType = int;
};

template<typename T>
typename Traits<T>::ReturnType g() { return T{}; }

int main() {
    static_assert(std::is_same<decltype(f<int>()), int>::value, "!");
    static_assert(std::is_same<decltype(f<double>()), double>::value, "!");
    static_assert(std::is_same<decltype(g<int>()), char>::value, "!");
    static_assert(std::is_same<decltype(g<char>()), int>::value, "!");
}

g的情况下,如果参数被推导,则两个看似相同的调用具有不同的返回类型。
也就是说,特化并不允许用户更改函数的声明。因此,您必须使用这样的定义来为不同的模板参数获得不同的返回类型。

0

1. 默认模板参数

您的情况"C"可以通过默认模板参数轻松解决:

template<typename T, typename U = int>
U foo(T x) {
    return x;
}

template<>
double foo<double, double>(double x) {
    return x;
}

现在,foo 可以像一个带有单个模板参数的函数一样使用:
auto a = foo(5);
auto b = foo(1.0);
auto c = foo<short>(5);

2. 类型映射

另一种方法相对比较丑陋,但是更具普适性。它要求您在一个地方枚举所有可能的返回类型,从而允许您选择任何一个返回类型,基于模板参数类型。这种解决方案的关键在于编译时类型映射。它可以使用成对、元组和tuple_element来实现,但是让我们停留在最简单的实现上:

struct last_key{};
struct last_value{};

template<typename key, typename k = last_key, typename v = last_value, typename ...pairs>
struct type_map {
    static_assert(sizeof...(pairs) % 2 == 0, "Last key does not have a value");
    using type = typename std::conditional<std::is_same<key, k>::value,
        v,
        typename type_map<key, pairs...>::type>::type;
};

template<typename key>
struct type_map<key> {
    using type = int;
};

对于给定的键类型,地图将“返回”值类型,如果未找到键,则返回int。有了地图,让我们声明一个取决于单个模板参数的返回类型:

template<typename key>
using return_type = typename type_map<key, double, double>::type;

最后,你的示例案例“C”将再次通过仅声明foo来解决:
template<typename T>
auto foo(T x) -> return_type<T> {
    return x;
}

但是如果你想的话,仍然可以添加具有不同行为的专业化,这将编译并正常工作:

// Specialization.
template<>
auto foo(double x) -> double {
    return x;
}

现在无论是否进行专业化,以下代码都是适用的:

auto a = foo(1);
auto b = foo(1.0);
std::cout << std::is_same<decltype(a), int>::value << std::endl;
std::cout << std::is_same<decltype(b), double>::value << std::endl;

将打印

1
1

使用默认模板参数和特化时,您的 auto b = foo(1.0); 不会使用特化,因此 b 的类型为 int - danadam

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