我无法理解模板实例化的顺序。似乎编译器不会考虑定义“太迟”的函数。以下步骤说明了以下代码的主要思想:
框架应该提供免费功能
convert<From, To>
如果它能找到函数的工作重载generate
.函数
to<T>
是convert<From,To>
的快捷方式并且只有在convert<From,To>
时才有效有效。用户应该能够提供
generate
的重载并能够使用to
和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
时有效出现在 convert
的定义之前和 to
.我该如何解决这个问题?
也许我的心智模型在这里是错误的,但当编译器看到 to<std::string>(uint16_t)
时我认为是模板,它开始倒退并根据需要实例化。任何指导将不胜感激。
最佳答案
当编译器看到 convert
和 to
的定义时,它不知道 generate
的存在,因为你已经知道了自己猜的。与您的想法相反,它并没有将 convert
和 to
的定义“搁置”,直到它看到 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
强制执行特定类型签名(请记住,编译器在编译 convert
和 to< 时需要检查类型
!)。如果你不想这样做,而且你可能不想这样做,那么最好的方法是期待一个进一步的模板参数来 convert
和 to
同样,你期望是可调用的,也就是说,您可以在函数调用中使用它:
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/