在依赖于参数的查找(或解决方法?)之前发生模板替换的任何方式
Any way for template substitution to happen before argument dependent lookup (or workarounds?)
我想这个问题的基本前提是我正在尝试将 enable_if
与参数相关查找 (ADL) 一起使用,但我不确定这是否可能。我确实在 this page 上看到
Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before template argument substitution (which may involve SFINAE) and overload resolution.
所以我想这行不通,但本着学习的精神,我想把问题提出来。
这是我正在尝试实现的示例:
#include <iostream>
namespace lib1 {
template <typename T>
void archive(T & t)
{
serialize(t);
}
}
namespace lib2 {
struct VectorInt {
int x;
int y;
};
struct VectorDouble {
double x;
double y;
};
template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
std::cout << vect.x << std::endl;
}
// maybe do something different with VectorDouble. Overloading would work,
// but I'm curious if it can be made to work with enable_if
}
int main() {
lib2::VectorInt myvect;
myvect.x = 2;
lib1::archive(myvect);
}
该示例大致基于我尝试对谷物库进行的操作。在我的例子中,我有几种不同类型的向量和矩阵,虽然我可以使用重载来使函数正确解析,但我很好奇使用 enable_if
功能来查看是否可以缩短代码。
无论如何,尝试编译会给出一条消息 "error: variable or field 'serialize' declared void"。
我的理解是这行不通,因为 enable_if
仅在参数相关查找后才计算?是吗?
对于那些想玩这个的人,我在 repl.it 上提供了代码:https://repl.it/repls/HalfBlandJumpthreading
您的示例中发生了两件不同的事情:(函数)模板参数推导,以及参数相关查找 (ADL)。如果您开始尝试显式指定模板参数(嘿,它是 C++),这两者之间的关系会稍微复杂一些,您可以在这里阅读更多内容:http://en.cppreference.com/w/cpp/language/adl(在注释部分)。
也就是说,一般来说,在 C++ 中,允许函数模板推导它们的参数通常比显式指定它们更好,这正是您在这里尝试做的,所以一切都很好。
当你这样做时:
namespace lib1 {
template <typename T>
void archive(T & t)
{
serialize(t);
}
}
对 serialize
的调用符合 ADL 的条件,并且由于它取决于 t 它被推迟到模板被实例化,因为需要 t
的类型(这称为 2 阶段查找) .当您使用 VectorInt
类型的对象调用 archive
时,对 serialize
的调用将在 VectorInt
的命名空间中查找。一切正常。问题出在这段代码中:
template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
std::cout << vect.x << std::endl;
}
您没有明确指定模板参数,因此必须推导它们。但是您在此处提供的表格不允许扣除:http://en.cppreference.com/w/cpp/language/template_argument_deduction,请参阅第一个示例的 non-deduced 上下文。为了更好地理解原因,请考虑您要求编译器执行的操作:您正在传递一个 VectorInt
并要求编译器找到 T
这样 std::enable_if<std::is_same<T, VectorInt>::value, T>::type>
恰好是 VectorInt
.这似乎是合理的,因为如果第一个参数为真,直觉上 enable_if
只是身份运算符(对于类型)。但是编译器对enable_if
没有特别的了解。这相当于说:找到 T
使得 Foo<T>::type
是 Bar
。编译器无法避免为每个 T 实例化 Foo
,这是不可能的。
我们想使用 enable_if,但不是以禁用推导的方式。使用 enable_if
的最佳方式通常是在默认模板参数中:
template<typename T, typename U = typename std::enable_if<std::is_same<T, VectorInt>::value>::type >
void serialize(T& vect) {
std::cout << vect.x << std::endl;
}
U
没有用于任何用途,但是当 serialize
传递一个 VectorInt
时,它现在将从传递的参数中推导出 T
,然后它将推断出具有默认值的 U。但是,如果enable_if参数为false则U不会对应任何类型,实例化为ill-formed: classic SFINAE.
这个答案已经很长了,但是enable_if
本身是一个相当深的话题;上面给出的形式在这里有效,但不适用于不相交的重载集。我建议阅读更多关于 ADL、模板参数推导、SFINAE 和 enable_if 的内容,而不仅仅是 SO(博客文章、Cppcon youtube 视频等)。
我想这个问题的基本前提是我正在尝试将 enable_if
与参数相关查找 (ADL) 一起使用,但我不确定这是否可能。我确实在 this page 上看到
Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before template argument substitution (which may involve SFINAE) and overload resolution.
所以我想这行不通,但本着学习的精神,我想把问题提出来。
这是我正在尝试实现的示例:
#include <iostream>
namespace lib1 {
template <typename T>
void archive(T & t)
{
serialize(t);
}
}
namespace lib2 {
struct VectorInt {
int x;
int y;
};
struct VectorDouble {
double x;
double y;
};
template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
std::cout << vect.x << std::endl;
}
// maybe do something different with VectorDouble. Overloading would work,
// but I'm curious if it can be made to work with enable_if
}
int main() {
lib2::VectorInt myvect;
myvect.x = 2;
lib1::archive(myvect);
}
该示例大致基于我尝试对谷物库进行的操作。在我的例子中,我有几种不同类型的向量和矩阵,虽然我可以使用重载来使函数正确解析,但我很好奇使用 enable_if
功能来查看是否可以缩短代码。
无论如何,尝试编译会给出一条消息 "error: variable or field 'serialize' declared void"。
我的理解是这行不通,因为 enable_if
仅在参数相关查找后才计算?是吗?
对于那些想玩这个的人,我在 repl.it 上提供了代码:https://repl.it/repls/HalfBlandJumpthreading
您的示例中发生了两件不同的事情:(函数)模板参数推导,以及参数相关查找 (ADL)。如果您开始尝试显式指定模板参数(嘿,它是 C++),这两者之间的关系会稍微复杂一些,您可以在这里阅读更多内容:http://en.cppreference.com/w/cpp/language/adl(在注释部分)。
也就是说,一般来说,在 C++ 中,允许函数模板推导它们的参数通常比显式指定它们更好,这正是您在这里尝试做的,所以一切都很好。
当你这样做时:
namespace lib1 {
template <typename T>
void archive(T & t)
{
serialize(t);
}
}
对 serialize
的调用符合 ADL 的条件,并且由于它取决于 t 它被推迟到模板被实例化,因为需要 t
的类型(这称为 2 阶段查找) .当您使用 VectorInt
类型的对象调用 archive
时,对 serialize
的调用将在 VectorInt
的命名空间中查找。一切正常。问题出在这段代码中:
template<typename T>
void serialize(std::enable_if<std::is_same<T, VectorInt>::value, T>::type & vect) {
std::cout << vect.x << std::endl;
}
您没有明确指定模板参数,因此必须推导它们。但是您在此处提供的表格不允许扣除:http://en.cppreference.com/w/cpp/language/template_argument_deduction,请参阅第一个示例的 non-deduced 上下文。为了更好地理解原因,请考虑您要求编译器执行的操作:您正在传递一个 VectorInt
并要求编译器找到 T
这样 std::enable_if<std::is_same<T, VectorInt>::value, T>::type>
恰好是 VectorInt
.这似乎是合理的,因为如果第一个参数为真,直觉上 enable_if
只是身份运算符(对于类型)。但是编译器对enable_if
没有特别的了解。这相当于说:找到 T
使得 Foo<T>::type
是 Bar
。编译器无法避免为每个 T 实例化 Foo
,这是不可能的。
我们想使用 enable_if,但不是以禁用推导的方式。使用 enable_if
的最佳方式通常是在默认模板参数中:
template<typename T, typename U = typename std::enable_if<std::is_same<T, VectorInt>::value>::type >
void serialize(T& vect) {
std::cout << vect.x << std::endl;
}
U
没有用于任何用途,但是当 serialize
传递一个 VectorInt
时,它现在将从传递的参数中推导出 T
,然后它将推断出具有默认值的 U。但是,如果enable_if参数为false则U不会对应任何类型,实例化为ill-formed: classic SFINAE.
这个答案已经很长了,但是enable_if
本身是一个相当深的话题;上面给出的形式在这里有效,但不适用于不相交的重载集。我建议阅读更多关于 ADL、模板参数推导、SFINAE 和 enable_if 的内容,而不仅仅是 SO(博客文章、Cppcon youtube 视频等)。