我有一组接收索引的模板函数(在示例中是int
)并返回给定类型的值,我使用SFINAE将std::string
与算术类型:
// 1
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
t(int) { ... }
// 2
template <typename T>
typename std::enable_if<std::is_same<std::string, T>::value, T>::type
t(int) { ... }
// 3
template <template <typename ...> class T, typename ... P>
T<P ...> t(int) { ... }
此外,还有一个功能,它接收一个容器并使用上述功能填充它:
template <typename C>
C c(int)
{
C r{};
std::insert_iterator<C> iterator(r, r.begin());
*iterator = t<typename C::value_type>(0);
return r;
}
t
的目标是区分数字和字符串,但如果提供了一对(因为它来自关联容器),那么, t
应使用第一种和第二种类型在两个不同的t
调用中拆分每对组件。
在反序列化非关联容器时,它可以工作,但使用关联容器编译失败:
using vi = std::vector<int>;
using mii = std::map<int, int>;
auto o = c<vi>(0); // Deserialize vector
auto p = c<mii>(0); // Deserialize map
在反序列化容器的一个元素时,编译失败了:
*iterator = t<typename C::value_type>(0);
对于非关联容器C::value_type
是一种类型,其中包含t
的前两个版本的条件之一,但对于关联容器C::value_type
是一对且应该失败t
的版本#1和#2,但t
函数的#3版本没有版本;问题在于它们中的三个失败了:
error: no matching function for call to 't' *iterator = t<typename C::value_type>(0); ^~~~~~~~~~~~~~~~~~~~~~~~~ note: in instantiation of function template specialization 'c<std::map<int, int>>' requested here auto p = c<mii>(0); ^ note: candidate template ignored: requirement 'std::is_arithmetic<pair<const int, int> >::value' was not satisfied [with T = std::pair<const int, int>] t(int) { ... } ^ note: candidate template ignored: requirement 'std::is_same<std::string, pair<const int, int> >::value' was not satisfied [with T = std::pair<const int, int>] t(int) { ... } ^ note: candidate template ignored: invalid explicitly-specified argument for template parameter 'T' T<P ...> t(int) { ... } ^
显然编译器抱怨缺少模板模板参数但是,如果我摆脱SFINAE,错误就会消失:
template <typename T>
T
t(int) { return {}; }
template <template <typename ...> class T, typename ... P>
T<P ...> t(int) { return {}; }
template <typename C>
C c(int)
{
C r{};
std::insert_iterator<C> iterator(r, r.begin());
*iterator = t<typename C::value_type>(0);
return r;
}
int main()
{
using vi = std::vector<int>;
using mii = std::map<int, int>;
auto o = c<vi>(0);
auto p = c<mii>(0);
// print 0
for (auto &v : o) std::cout << v << '\n';
// print 00
for (auto &v : p) std::cout << v.first << v.second << '\n';
return 0;
}
看起来SFINAE强制要求模板模板参数而不是推导出来,为什么会发生这种情况?我该如何解决?
代码可在 Wandbox 三へ( へ՞ਊ ՞)へ ハッハッ 。
中找到答案 0 :(得分:2)
看起来(根据您的评论和编辑),您希望根据给定的模板参数执行不同的功能。最简单的方法是使用类,因为类在专业化方面更灵活。以下是您可以做的一个小例子:
// initial declaration (without definition), the second template
// parameter will be used to enable some specializations
template <class T, class = void>
struct deserializer;
// specialization for arithmetic types
template <class T>
struct deserializer<
T, std::enable_if_t<std::is_arithmetic<T>::value>> {
T operator()() const {
}
};
// specialization for std::string
template <>
struct deserializer<std::string> {
std::string operator()() const {
}
};
// specialization for std::pair<U, V>
template <class U, class V>
struct deserializer<std::pair<U, V>> {
std::pair<U, V> operator()() const {
}
};
然后在你的函数c
中:
deserializer<typename C::value_type> ds;
*iterator = ds();
如果您不想每次都创建deserializer
类型的对象,也可以添加中间通用函数:
template <class T>
T deserialize() {
return deserializer<T>{}();
}
但我认为你的目标是反序列化多个对象,因此在这种情况下使用仿函数并不是那么糟糕。
为什么扣除在您的情况下失败?
实际上,由于演绎适用于参数并且您使用的是返回类型,因此此处没有任何推论。这里的问题是t
的实例化:
t<std::pair<int, int>>
...永远不会匹配t
的声明:
template <template <class... > class, class... >
auto t();
因为你需要:
t<std::pair, int, int>
...匹配此类模板签名。可以使用t<typename C::value_type>
匹配的唯一模板签名是以下形式的签名:
template <class T, /* something */>
...其中/* something */
是可变参数模板参数(class...
),或默认模板参数列表(class X = void
,int N = 0
)或其组合两者都有。
答案 1 :(得分:1)
此处的问题是原始t
和新t
具有不同的模板参数:
// original.
template <template <typename ...> class T, typename ... P>
T<P ...> t(int) { ... }
// new.
template <typename C>
C c(int)
请注意,原始t
不仅具有(可能)多于1个模板参数,而且第一个参数是模板模板参数,而不是类型参数。
你也似乎对模板参数推断感到困惑。模板参数推导从函数参数中推导出模板参数。您的所有函数都有一个int
参数,因此不会进行任何演绎。
换句话说,t<typename C::value_type>(0)
无法使用原始函数,因为std::pair<const int, int>
不是有效的模板模板参数。您需要写t<std::pair, const int, int>(0)
。
如果您的问题是如何使用SFINAE接受“容器”(不是真的,因为容器可以有非类型模板参数),那么这应该有效:
template<typename T>
struct is_container : std::false_type { };
template<template<typename...> class C, typename... Ts>
struct is_container<C<Ts...>> : std::true_type { };
template <typename T>
typename std::enable_if<is_container<T>::value, T>::type
t(int) { ... }