c++ - 模板实例化期间函数定义的顺序

标签 c++ templates c++11 instantiation

我无法理解模板实例化的顺序。似乎编译器不会考虑定义“太迟”的函数。以下步骤说明了以下代码的主要思想:

  1. 框架应该提供免费功能convert<From, To>如果它能找到函数的工作重载 generate .

  2. 函数to<T>convert<From,To> 的快捷方式并且只有在 convert<From,To> 时才有效有效。

  3. 用户应该能够提供 generate 的重载并能够使用toconvert .

对应代码:

#include <string>
#include <utility>
#include <iostream>

// If I move the code down below at [*] to this location, everything works as
// expected.

// ------------- Framework Code -------------

// Anything that can be generated can also be converted to a string.
template <typename From>
auto convert(From const& from, std::string& to)
  -> decltype(
      generate(std::declval<std::back_insert_iterator<std::string>&>(), from)
      )
{
  to.clear();
  auto i = std::back_inserter(to);
  return generate(i, from);
}

// Similar to convert, except that it directly returns the requested type.
template <typename To, typename From>
auto to(From const& f) -> decltype(convert(f, std::declval<To&>()), To())
{
  To t;
  if (! convert(f, t))
    throw std::invalid_argument("invalid conversion");
  return t;
}

// ------------- User Code -------------

// [*] Support arithmetic types.
template <typename Iterator, typename T>
auto generate(Iterator& out, T i)
  -> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type
{
  // Note: I merely use std::to_string for illustration purposes here.
  auto str = std::to_string(i);
  out = std::copy(str.begin(), str.end(), out);
  return true;
}

int main()
{
  uint16_t s = 16;
  std::cout << to<std::string>(s) << std::endl;
  return 0;
}

以下代码中的问题在于它仅在函数 generate 时有效出现在 convert 的定义之前to .我该如何解决这个问题?

也许我的心智模型在这里是错误的,但当编译器看到 to<std::string>(uint16_t) 时我认为是模板,它开始倒退并根据需要实例化。任何指导将不胜感激。

最佳答案

当编译器看到 convertto 的定义时,它不知道 generate 的存在,因为你已经知道了自己猜的。与您的想法相反,它并没有将 convertto 的定义“搁置”,直到它看到 generate 是什么.要解决此问题,您需要转发声明 生成,可以使用以下构造来完成:

template <typename Iterator, typename T>
auto generate(Iterator& out, T i)
  -> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type;

这应该出现在 convert 的定义之前,以便编译器在编译 convert< 时知道 generate 确实存在并且也是一个函数。这样编译器就可以检查语法并保证它是对 generate 的有效调用,甚至在它知道 generate 实际做什么之前,因为此时它需要做的就是检查根据语言标准定义的规则,参数和返回值匹配。

通过这样做,您自然会为 generate 强制执行特定类型签名(请记住,编译器在编译 convertto< 时需要检查类型!)。如果你不想这样做,而且你可能不想这样做,那么最好的方法是期待一个进一步的模板参数来 convertto 同样,你期望是可调用的,也就是说,您可以在函数调用中使用它:

template <typename From, typename Generator>
auto convert(From const& from, std::string& to, Generator generate)
  -> decltype(
      generate(std::declval<std::back_insert_iterator<std::string>&>(), from)
      )
{
  to.clear();
  auto i = std::back_inserter(to);
  return generate(i, from);
}

这类对象通常称为可调用对象

这种方法的缺点在于,不幸的是,由于 C++ 尚不支持概念,因此您无法执行可调用对象generate 应满足的要求。 .尽管如此,std 库成功地将这种方法用于其算法

这种方法的优点是它可以非常灵活,对于任何可能的可调用对象,可以使用它最少地关注类型要求。这包括自由函数、函数对象、通过绑定(bind) 的成员函数,等等。更不用说用户可以完全自由地为她的可调用对象 选择她想要的名称,而不是被迫使用generate,因为如果它是有效的,您的初始想法将需要C++.

现在要使用您定义的自由函数 generate 调用这个修改后的 convert 版本,您可以这样做:

to<std::string>(s, generate<std::back_insert_iterator<std::string>, uint16_t>);

这不是很好,因为您必须显式声明模板参数,这种方法无法充分利用generate 是模板函数这一事实。幸运的是,这种不便可以通过使用函数对象来克服,例如:

struct Generator
{
    template <typename Iterator, typename T>
    auto operator()(Iterator& out, T i)
      -> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type
    {
      // Note: I merely use std::to_string for illustration purposes here.
      auto str = std::to_string(i);
      out = std::copy(str.begin(), str.end(), out);
      return true;
    }
};

之前的调用会变得简单

to<std::string>(s, Generator());

充分利用其模板特性。

无论如何,如果我的想法是正确的,这部分代码是用户的责任,所以她应该有完全的自主权来决定她喜欢哪种方式。

关于c++ - 模板实例化期间函数定义的顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18603791/

相关文章:

c++ - 比较以 char 形式出现的字符串**

c++ - C++模板类问题中的类型条件

c++ - 避免模​​棱两可的部分特化

c++ - 跨平台线程亲和函数

c++ - C++11 中的全局常量

c++ - Type_traits *_v 变量模板实用程序顺序编译失败

c++ - 作为模板参数的(任何类型的)值列表

C++ SFINAE enable_if_t 在成员函数中,如何消除歧义?

c++ - 为什么我们可以使用空括号来初始化 std::function<void(Node*)> 离开?

c++ - Qt中如何通过QThread管理主窗口