模板替换的任何方式都可以在参数依赖查找之前发生(或解决方法?)

时间:2018-03-17 02:55:52

标签: c++ templates argument-dependent-lookup cereal

我认为这个问题的基本前提是我正在尝试使用enable_if和Argument Dependent Lookup(ADL),但我不确定它是否可行。我确实在this page看到了

  

模板参数推导发生在函数模板名称查找(可能涉及依赖于参数的查找)之后和模板参数替换(可能涉及SFINAE)和重载解析之前。

所以我想这不行,但本着学习的精神,我想把问题放在那里。

以下是我想要实现的一个例子:

#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功能来查看是否可以缩短代码。

无论如何,尝试编译会给出一条消息“错误:变量或字段'serialize'声明为void”。

我的理解是,这不起作用,因为enable_if仅在参数依赖查找后进行评估?是吗?

对于那些想要玩这个的人,我在repl.it上有代码:https://repl.it/repls/HalfBlandJumpthreading

1 个答案:

答案 0 :(得分:1)

在你的例子中有两个不同的事情:有(函数)模板参数推导,还有参数依赖查找(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阶段查找)。当您使用archive类型的对象呼叫VectorInt时,对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,参见非推断的上下文,这是第一个例子。为了更好地理解原因,请考虑您要求编译器执行的操作:您传递VectorInt并要求编译器查找T,使std::enable_if<std::is_same<T, VectorInt>::value, T>::type>恰好是{{1} }}。这似乎是合理的,因为如果第一个参数为true,直觉VectorInt只是身份运算符(对于类型)。但是编译器对enable_if没有特别的了解。这相当于说:查找enable_ifTFoo<T>::type。编译器无法为每个T实例化Bar,这是不可能的。

我们想要使用enable_if,但不能禁用扣除。使用Foo的最佳方法通常是默认模板参数:

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,然后它将推断出U有默认值。但是,如果enable_if参数为false,则U将不对应任何类型,并且实例化格式不正确:经典SFINAE。

这个答案已经很长了,但T本身是一个相当深刻的话题;上面给出的表单在这里工作,但不适用于不相交的重载集。我建议阅读更多有关ADL,模板参数演绎,SFINAE和enable_if的内容,不仅仅是SO(博客文章,Cppcon youtube视频等)。