模板参数推导不一致
Template argument deduction inconsistent
以下代码在 MSVC 2019 和 Clang t运行k 上编译和 运行s。 (我认为它至少需要 C++17)。它在 gcc-t运行k 上没有 运行,我相信共识是这是由于 gcc 中的一个错误。
但是,当任何元素被用户类型或指针类型替换时,它在所有编译器上都会失败。要查看此内容,请取消注释末尾附近的 tuple_c
定义。
我真的有点惊讶它的工作原理,因为它似乎专门化了一个带有 universal-ref 参数的函数和一个带有 r-value-ref 参数的函数。也许这没关系?如果是,为什么结构失败了?
有没有更好的写法?我的意思是一般来说。我很清楚 std::tuple
.
#include <iostream>
using namespace std;
template <typename... TP> class Tuple
{
};
template <> class Tuple <>
{
};
template <typename Head, typename... Tail> class Tuple <Head, Tail...>
{
Head head;
Tuple <Tail...> tail;
public:
Tuple ()
{
}
Tuple (const Head& head_in, const Tail&...tail_in)
: head (head_in), tail (tail_in...)
{
}
template <int i> auto Get ()
{
return tail.template Get <i-1> ();
}
template <> auto Get <0> ()
{
return head;
}
template <int i, typename T> void Set (T&& v) // T&& is a universal ref
{
tail.template Set <i-1, T> (static_cast <T&&> (v));
}
template <int i, typename T> void Set (const T& v)
{
tail.template Set <i-1, T> (v);
}
template <> void Set <0, Head> (Head&& v) // Head&& is an rv-ref
{
head = v;
}
template <> void Set <0, Head> (const Head& v)
{
head = v;
}
};
template <typename Head, typename... Tail> Tuple <Head, Tail...> MakeTuple (Head&& head, Tail&&...tail)
{
Tuple <Head, Tail...> result (head, tail...);
return result;
}
struct S
{
int x;
int y;
};
ostream& operator << (ostream& out, const S& s)
{
out << "{" << s.x << "," << s.y << "}";
return out;
}
int main(int argc, char* argv[])
{
auto tuple_a = MakeTuple (1,2,3,4);
tuple_a.Set <1,int> (42);
cout << tuple_a.Get <0> () << '\n';
cout << tuple_a.Get <1> () << '\n';
cout << tuple_a.Get <2> () << '\n';
cout << tuple_a.Get <3> () << '\n';
auto tuple_b = MakeTuple (1,2.3f,3,4);
tuple_b.Set <1,float> (42.3f);
cout << tuple_b.Get <0> () << '\n';
cout << tuple_b.Get <1> () << '\n';
cout << tuple_b.Get <2> () << '\n';
cout << tuple_b.Get <3> () << '\n';
S s {4,5};
//auto tuple_c = MakeTuple (1,2.3f,3,s);
return 0;
}
首先,在 CWG 727 之前,您不能在 class 范围内特化成员函数模板。您将不得不使用 constexpr-if
、标签调度或 SFINAE 来处理 i==0
案例。
在 c++14 中使用 std::enable_if_t
那将是:
template <int i, typename T>
std::enable_if_t<i != 0> Set(T&& v)
{
tail.template Set<i-1>(static_cast<T&&>(v));
}
template <int i, typename T>
std::enable_if_t<i == 0> Set(T&& v)
{
head = static_cast<T&&>(v);
}
在c++17中使用constexpr-if
变成:
template <int i, typename T>
void Set(T&& v)
{
if constexpr (i == 0) head = static_cast<T&&>(v);
else tail.template Set<i-1>(static_cast<T&&>(v));
}
其次,一旦编译器允许您在 class 函数中专门化函数模板,您当前的方法就会出现另一个问题。由于模板参数推导如何用于转发引用,您的 MakeTuple
实现创建了一个引用类型元组,对应于那些 MakeTuple
左值参数:
template <typename Head, typename... Tail>
Tuple<Head, Tail...> MakeTuple(Head&& head, Tail&&... tail);
这使您的 comment/assumption:
void Set<0, Head>(Head&& v) // Head&& is an rv-ref
无效。
也就是说,对于左值表达式 s
:
S s{ 4, 5 };
MakeTuple(s);
推导出的Head
是S&
(也是引用折叠后head
的类型)。然后编译器尝试实例化 Tuple<S&>
并生成以下两个声明:
void Set<0, S&>(S& && v);
void Set<0, S&>(S& const& v);
参考折叠后它以:
结束
void Set<0, S&>(S& v);
void Set<0, S&>(S& v);
此时,不仅两个定义相同,而且编译器无法决定哪个主要函数模板:
template <int i, typename T>
void Set(T&& v);
template <int i, typename T>
void Set(const T& v);
它们是的特化,因为使用 T=S&
匹配两者。这可以通过在将每种类型存储在元组中之前对其进行衰减来解决:
template <typename Head, typename... Tail>
Tuple<std::decay_t<Head>, std::decay_t<Tail>...> MakeTuple(Head&& head, Tail&&... tail);
我的问题在上面得到了回答,但我认为包含解决方案的完整代码可能会有用。它现在适用于除数组和字符串文字之外的所有类型。
#include <iostream>
using namespace std;
template <typename... TP> class Tuple
{
};
template <> class Tuple <>
{
};
template <typename Head, typename... Tail> class Tuple <Head, Tail...>
{
Head head;
Tuple <Tail...> tail;
public:
Tuple ()
{
}
Tuple (const Head& head_in, const Tail&...tail_in)
: head (head_in), tail (tail_in...)
{
}
template <int i> auto Get ()
{
return tail.template Get <i-1> ();
}
template <> auto Get <0> ()
{
return head;
}
template <int i, typename T> void Set (T&& v)
{
tail.template Set <i-1, T> (static_cast <T&&> (v));
}
template <int i, typename T> void Set (const T& v)
{
tail.template Set <i-1, typename std::decay<T>::type> (v);
}
template <> void Set <0, typename std::decay<Head>::type> (Head&& v)
{
head = v;
}
template <> void Set <0, typename std::decay<Head>::type> (const Head& v)
{
head = v;
}
};
template <typename Head, typename...Tail> Tuple <typename std::decay <Head>::type, typename std::decay<Tail>::type...> MakeTuple (Head&& head, Tail&&...tail)
{
Tuple <typename std::decay <Head>::type, typename std::decay<Tail>::type...> result (head, tail...);
return result;
}
struct S
{
int x;
int y;
};
ostream& operator << (ostream& out, const S& s)
{
out << "{" << s.x << "," << s.y << "}";
return out;
}
int main(int argc, char* argv[])
{
const char* p = "hello";
S s;
int v = 32;
auto tuple_a = MakeTuple (1.0,v,p,s);
cout << tuple_a.Get <0> () << endl;
cout << tuple_a.Get <1> () << endl;
cout << tuple_a.Get <2> () << endl;
cout << tuple_a.Get <3> () << endl;
S s_update {10,12};
tuple_a.Set <3> (s_update);
const char* p_update = "goodbye";
tuple_a.Set <2> (p_update);
cout << tuple_a.Get <0> () << endl;
cout << tuple_a.Get <1> () << endl;
cout << tuple_a.Get <2> () << endl;
cout << tuple_a.Get <3> () << endl;
}
以下代码在 MSVC 2019 和 Clang t运行k 上编译和 运行s。 (我认为它至少需要 C++17)。它在 gcc-t运行k 上没有 运行,我相信共识是这是由于 gcc 中的一个错误。
但是,当任何元素被用户类型或指针类型替换时,它在所有编译器上都会失败。要查看此内容,请取消注释末尾附近的 tuple_c
定义。
我真的有点惊讶它的工作原理,因为它似乎专门化了一个带有 universal-ref 参数的函数和一个带有 r-value-ref 参数的函数。也许这没关系?如果是,为什么结构失败了?
有没有更好的写法?我的意思是一般来说。我很清楚 std::tuple
.
#include <iostream>
using namespace std;
template <typename... TP> class Tuple
{
};
template <> class Tuple <>
{
};
template <typename Head, typename... Tail> class Tuple <Head, Tail...>
{
Head head;
Tuple <Tail...> tail;
public:
Tuple ()
{
}
Tuple (const Head& head_in, const Tail&...tail_in)
: head (head_in), tail (tail_in...)
{
}
template <int i> auto Get ()
{
return tail.template Get <i-1> ();
}
template <> auto Get <0> ()
{
return head;
}
template <int i, typename T> void Set (T&& v) // T&& is a universal ref
{
tail.template Set <i-1, T> (static_cast <T&&> (v));
}
template <int i, typename T> void Set (const T& v)
{
tail.template Set <i-1, T> (v);
}
template <> void Set <0, Head> (Head&& v) // Head&& is an rv-ref
{
head = v;
}
template <> void Set <0, Head> (const Head& v)
{
head = v;
}
};
template <typename Head, typename... Tail> Tuple <Head, Tail...> MakeTuple (Head&& head, Tail&&...tail)
{
Tuple <Head, Tail...> result (head, tail...);
return result;
}
struct S
{
int x;
int y;
};
ostream& operator << (ostream& out, const S& s)
{
out << "{" << s.x << "," << s.y << "}";
return out;
}
int main(int argc, char* argv[])
{
auto tuple_a = MakeTuple (1,2,3,4);
tuple_a.Set <1,int> (42);
cout << tuple_a.Get <0> () << '\n';
cout << tuple_a.Get <1> () << '\n';
cout << tuple_a.Get <2> () << '\n';
cout << tuple_a.Get <3> () << '\n';
auto tuple_b = MakeTuple (1,2.3f,3,4);
tuple_b.Set <1,float> (42.3f);
cout << tuple_b.Get <0> () << '\n';
cout << tuple_b.Get <1> () << '\n';
cout << tuple_b.Get <2> () << '\n';
cout << tuple_b.Get <3> () << '\n';
S s {4,5};
//auto tuple_c = MakeTuple (1,2.3f,3,s);
return 0;
}
首先,在 CWG 727 之前,您不能在 class 范围内特化成员函数模板。您将不得不使用 constexpr-if
、标签调度或 SFINAE 来处理 i==0
案例。
在 c++14 中使用 std::enable_if_t
那将是:
template <int i, typename T>
std::enable_if_t<i != 0> Set(T&& v)
{
tail.template Set<i-1>(static_cast<T&&>(v));
}
template <int i, typename T>
std::enable_if_t<i == 0> Set(T&& v)
{
head = static_cast<T&&>(v);
}
在c++17中使用constexpr-if
变成:
template <int i, typename T>
void Set(T&& v)
{
if constexpr (i == 0) head = static_cast<T&&>(v);
else tail.template Set<i-1>(static_cast<T&&>(v));
}
其次,一旦编译器允许您在 class 函数中专门化函数模板,您当前的方法就会出现另一个问题。由于模板参数推导如何用于转发引用,您的 MakeTuple
实现创建了一个引用类型元组,对应于那些 MakeTuple
左值参数:
template <typename Head, typename... Tail>
Tuple<Head, Tail...> MakeTuple(Head&& head, Tail&&... tail);
这使您的 comment/assumption:
void Set<0, Head>(Head&& v) // Head&& is an rv-ref
无效。
也就是说,对于左值表达式 s
:
S s{ 4, 5 };
MakeTuple(s);
推导出的Head
是S&
(也是引用折叠后head
的类型)。然后编译器尝试实例化 Tuple<S&>
并生成以下两个声明:
void Set<0, S&>(S& && v);
void Set<0, S&>(S& const& v);
参考折叠后它以:
结束void Set<0, S&>(S& v);
void Set<0, S&>(S& v);
此时,不仅两个定义相同,而且编译器无法决定哪个主要函数模板:
template <int i, typename T>
void Set(T&& v);
template <int i, typename T>
void Set(const T& v);
它们是的特化,因为使用 T=S&
匹配两者。这可以通过在将每种类型存储在元组中之前对其进行衰减来解决:
template <typename Head, typename... Tail>
Tuple<std::decay_t<Head>, std::decay_t<Tail>...> MakeTuple(Head&& head, Tail&&... tail);
我的问题在上面得到了回答,但我认为包含解决方案的完整代码可能会有用。它现在适用于除数组和字符串文字之外的所有类型。
#include <iostream>
using namespace std;
template <typename... TP> class Tuple
{
};
template <> class Tuple <>
{
};
template <typename Head, typename... Tail> class Tuple <Head, Tail...>
{
Head head;
Tuple <Tail...> tail;
public:
Tuple ()
{
}
Tuple (const Head& head_in, const Tail&...tail_in)
: head (head_in), tail (tail_in...)
{
}
template <int i> auto Get ()
{
return tail.template Get <i-1> ();
}
template <> auto Get <0> ()
{
return head;
}
template <int i, typename T> void Set (T&& v)
{
tail.template Set <i-1, T> (static_cast <T&&> (v));
}
template <int i, typename T> void Set (const T& v)
{
tail.template Set <i-1, typename std::decay<T>::type> (v);
}
template <> void Set <0, typename std::decay<Head>::type> (Head&& v)
{
head = v;
}
template <> void Set <0, typename std::decay<Head>::type> (const Head& v)
{
head = v;
}
};
template <typename Head, typename...Tail> Tuple <typename std::decay <Head>::type, typename std::decay<Tail>::type...> MakeTuple (Head&& head, Tail&&...tail)
{
Tuple <typename std::decay <Head>::type, typename std::decay<Tail>::type...> result (head, tail...);
return result;
}
struct S
{
int x;
int y;
};
ostream& operator << (ostream& out, const S& s)
{
out << "{" << s.x << "," << s.y << "}";
return out;
}
int main(int argc, char* argv[])
{
const char* p = "hello";
S s;
int v = 32;
auto tuple_a = MakeTuple (1.0,v,p,s);
cout << tuple_a.Get <0> () << endl;
cout << tuple_a.Get <1> () << endl;
cout << tuple_a.Get <2> () << endl;
cout << tuple_a.Get <3> () << endl;
S s_update {10,12};
tuple_a.Set <3> (s_update);
const char* p_update = "goodbye";
tuple_a.Set <2> (p_update);
cout << tuple_a.Get <0> () << endl;
cout << tuple_a.Get <1> () << endl;
cout << tuple_a.Get <2> () << endl;
cout << tuple_a.Get <3> () << endl;
}