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

时间:2013-09-04 00:58:56

标签: c++ templates c++11 instantiation

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

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

  2. 功能to<T>convert<From,To>的快捷方式,仅在convert<From,To>有效时才有效。

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

  4. 相应的代码:

    #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;
    }
    

    以下代码中的问题是,只有在generateconvert的定义之前出现时,它才有效。我该如何解决这个问题?

    也许我的心理模型在这里是错误的,但是当编译器看到to时我想到了模板,它开始倒退并根据需要进行实例化。任何指导将不胜感激。

1 个答案:

答案 0 :(得分:1)

当您看到generateconvert的定义时,编译器不知道to是否存在,正如您已经猜到的那样。与您的想法相反,它不会将convertto的定义置于“保持不变”状态,直到它看到generate为止。要解决此问题,您需要转发声明 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强制执行特定类型签名(请记住编译器在编译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 ++遗憾的是还不支持 concepts ,所以你无法强制执行可调用对象generate应该遵守的要求。尽管如此,这种方法是std库成功用于算法的方法。

这种方法的优点在于它可以非常灵活,可以使用任何可能的可调用对象,它可以最小程度地满足类型要求。这包括免费功能,功能对象,通过绑定等成员功能。更不用说用户可以完全自由地为她的可调用对象选择她想要的名称,而不是被迫使用generate,因为如果它是有效的c ++,你的初始想法将需要。 / p>

现在使用您定义的免费函数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());

充分利用其模板性质。

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