0

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

  1. convert<From, To>如果框架可以找到该函数的工作重载,则该框架应该提供一个自由函数generate

  2. 该函数to<T>是一个快捷方式,convert<From,To>并且仅在有效时才convert<From,To>有效。

  3. 用户应该能够提供重载generate并能够使用toand convert

对应代码:

#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出现convertand的定义之前才有效to。我该如何解决这个问题?

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

4

1 回答 1

1

编译器在看到andgenerate的定义时并不知道 的存在,正如您自己已经猜到的那样。与您的想法相反,它并没有将和“搁置”的定义,直到它看到是什么。要解决此问题,您需要转发 declare,可以使用以下构造来完成:converttoconverttogenerate generate

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

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

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

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应该关注的要求。尽管如此,这种方法是标准库成功用于其算法的方法。

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

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

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());

充分利用其模板性质。

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

于 2013-09-04T01:24:56.683 回答