C++ 函数模板部分特化?
Posted
技术标签:
【中文标题】C++ 函数模板部分特化?【英文标题】:C++ function template partial specialization? 【发布时间】:2011-12-25 01:50:33 【问题描述】:我知道下面的代码是一个类的部分特化:
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 Express!如果不是,那么您能否解释一下部分专业化的概念?
#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;
【问题讨论】:
寻找类专业化的类比。如果它被称为类专业化,那为什么我应该将函数视为重载?? 不,专业化语法不同。在下面的答案中查看(假定的)函数专业化语法。 为什么这不会引发“调用最大值不明确”的错误?max(5,5)
如何解析为 max(T const&, T const&) [with T=int]
而不是 max(T1 const&, T2 const&) [with T1=int and T2=int]
?
【参考方案1】:
根据标准,功能部分特化是不允许的。在示例中,您实际上是在重载而不是专门化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
return 10;
在函数模板的情况下,C++ 标准只允许完全特化, -- 不包括编译器扩展!
【讨论】:
@Narek,部分功能专业化不是标准的一部分(无论出于何种原因)。我认为 MSVC 支持它作为扩展。可能在某个时间之后,其他编译器也会允许这样做。 @iammilind:没问题。他似乎已经知道这一点。这就是为什么他也在尝试将其用于功能模板。所以我再次编辑它,现在清楚了。 谁能解释为什么不允许部分专业化? @NHDaly,它不会给出歧义错误,因为 1 个函数比另一个函数更匹配。为什么它选择(T, T)
而不是(T1, T2)
为(int, int)
,是因为前者保证有2个参数并且两种类型相同;后者仅保证有 2 个参数。编译器总是选择一个准确的描述。例如如果你必须在两条关于“河流”的描述之间做出选择,你会选择哪一种? “集水”与“集水”。
@kfsone,我认为这个功能正在审查中,因此可以解释。你可以参考this open-std section,我在Why does the C++ standard not allow function template partial specialization?看到的【参考方案2】:
由于不允许部分专业化 - 正如其他答案所指出的那样 - 您可以使用 std::is_same
和 std::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
编辑:如果您需要能够处理剩下的所有其他案例,您可以添加一个定义,说明已处理的案例不应匹配 --否则你会陷入模棱两可的定义。定义可以是:
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
虽然这个all-cases的东西看起来有点无聊,因为你必须告诉编译器你已经完成的所有事情,处理多达5个或更多的特化是完全可行的。
【讨论】:
确实没有必要这样做,因为这可以通过函数重载以更简单、更清晰的方式来处理。 @Adrian 我真的想不出任何其他函数重载方法来解决这个问题。您注意到不允许部分重载,对吗?如果您认为更清楚,请与我们分享您的解决方案。 还有其他方法可以轻松捕获所有模板化函数吗? @Adrian 确实在某些情况下可以重新定义typed_foo
,使其只需要一个模板参数而不是两个,然后像你说的那样使用重载。但是,这并不是 OP 真正要问的。此外,我不确定您是否可以纯粹通过重载来实现包罗万象的功能。此外,您可能希望您的包罗万象的实现在使用时导致编译错误,这仅适用于模板函数,其中依赖于模板的行会导致编译器发出错误。【参考方案3】:
什么是专业化?
如果你真的想了解模板,你应该看看函数式语言。 C++ 中的模板世界是一种纯粹的函数式子语言。
在函数式语言中,选择是使用模式匹配:
-- 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 声明,说明参数的数量和性质。它可以只是一个声明,也可以作为定义(您的选择),然后您可以(如果您愿意)提供该模式的特化并将其与该类的不同(否则将是愚蠢的)版本相关联.
对于模板函数,特化有些尴尬:它与重载决议有些冲突。因此,已决定专业化将与非专业化版本相关,并且在重载解决期间不会考虑专业化。因此,选择正确函数的算法变为:
-
在常规函数和非专用模板之间执行重载解析
如果选择了非特化模板,请检查是否存在与其匹配更好的特化
(深度治疗见GotW #49)
因此,函数的模板特化是第二区公民(字面意思)。就我而言,没有它们我们会更好:我还没有遇到过模板专业化使用无法通过重载来解决的情况。
这是模板专业化吗?
不,这只是过载,这很好。事实上,重载通常会按照我们的预期工作,而专业化可能会令人惊讶(请记住我链接的 GotW 文章)。
【讨论】:
"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."
非类型模板参数怎么样?
@Julius:你仍然可以使用重载,尽管引入了一个虚拟参数,例如boost::mpl::integral_c<unsigned, 3u>
。另一个解决方案也可以是使用enable_if
/disable_if
,尽管情况不同。【参考方案4】:
不允许非类、非变量的偏特化,但如前所述:
电脑的所有问题 科学可以解决 另一个层次的间接性。 ——大卫·惠勒
添加一个类来转发函数调用可以解决这个问题,这里是一个例子:
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, "");
【讨论】:
【参考方案5】:没有。例如,您可以合法地专门化std::swap
,但不能合法地定义自己的重载。这意味着您不能让 std::swap
为您自己的自定义类模板工作。
在某些情况下,重载和部分特化可以产生相同的效果,但远非如此。
【讨论】:
这就是为什么将swap
重载放在命名空间中的原因。【参考方案6】:
迟到的答案,但一些迟到的读者可能会发现它很有用:有时,辅助函数(设计为可以专门化)也可以解决问题。
让我们想象一下,这就是我们试图解决的问题:
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);
好的,部分模板函数特化,我们不能这样做......所以让我们将特化所需的部分“导出”到辅助函数中,特化那个并使用它:
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,... - 并不是说这些不好或我的更好,只是 另一个一个)将共享相当多的通用代码。
【讨论】:
以上是关于C++ 函数模板部分特化?的主要内容,如果未能解决你的问题,请参考以下文章
C++模板详解:泛型编程模板原理非类型模板参数模板特化分离编译