使用函数重载将行为注入模板函数的规则
Rules of injecting behavior to templated function using function overloading
在编写模板化库时,有时希望在函数模板定义之后实现行为。例如,我正在考虑实现为
的记录器库中的 log
函数
template< typename T >
void log(const T& t) {
std::cout << "[log] " << t << std::endl;
}
以后如果我想在我自己的类型上使用 log
,我会实现 std::ostream& operator<<(std::ostream&, CustomType)
并希望 log
函数自动在 CustomType
上运行。
但是,我很不确定这个模式是否符合标准。为了了解编译器如何处理它,我编写了以下最小示例。
#include<iostream>
// In some library...
void foo(double) { std::cout << "double" << std::endl; }
template< typename T>
void doFoo(T x) {
foo(x);
}
// In some codes using the library...
struct MyClass {};
template< typename T > struct MyClassT {};
namespace my { struct Class {}; }
void foo(MyClass) { std::cout << "MyClass" << std::endl; }
void foo(MyClassT<int>) { std::cout << "MyClassT<int>" << std::endl; }
void foo(my::Class) { std::cout << "my::Class" << std::endl; }
void foo(int) { std::cout << "int" << std::endl; }
int main() {
doFoo(1.0); // okay, prints "double".
doFoo(MyClass{}); // okay, prints "MyClass".
doFoo(MyClassT<int>{}); // okay, prints "MyClassT<int>".
doFoo(42); // not okay, prints "double". int seems to have been converted to double.
// doFoo(my::Class{}); // compile error, cannot convert my::Class to int.
return 0;
}
我希望通过重载 foo
函数注入到 doFoo
函数模板。结果看起来很不一致,因为它适用于自定义(模板化)类型,但不适用于命名空间或内置类型中的自定义类型。对于编译器 MSVC(与 Visual Studio 16.10.1 捆绑)以及 gcc 9.3.0.
,结果相同
我现在很困惑什么才是正确的行为。我猜这与实例化的位置有关。我的问题是:
- 以上代码合法吗?或者它们格式不正确?
- 如果代码是合法的,是什么导致了不同重载的不一致行为?
- 如果代码是非法的,什么是注入库模板的好替代方法? (我正在考虑将 functions/functors 显式传递给我的
doFoo
函数,就像 <algorithm>
所做的那样。)
If the codes are illegal, what would be a good alternative to injecting library templates? (I'm thinking of passing functions/functors explicitly to my doFoo
function, like what <algorithm>
is doing.)
您可以将仿函数与默认特征类型结合使用以获得“两全其美”。这基本上就是标准库中无序容器使用 std::hash
.
的方式
它允许通过专门化特征或显式传递仿函数来进行注入。
#include<iostream>
// In some library...
template <typename T>
struct lib_trait;
template<>
struct lib_trait<double> {
void operator()(double) const { std::cout << "double" << std::endl; }
};
template<typename T, typename CbT=lib_trait<T>>
void doFoo(T x, const CbT& cb={}) {
cb(x);
}
// In some codes using the library...
struct MyClass {};
template< typename T > struct MyClassT {};
namespace my { struct Class {}; }
template<>
struct lib_trait<MyClass> {
void operator()(MyClass) const { std::cout << "MyClass" << std::endl; }
};
template<>
struct lib_trait<MyClassT<int>> {
void operator()(MyClassT<int>) const { std::cout << "MyClassT<int>" << std::endl; }
};
template<>
struct lib_trait<int> {
void operator()(int) const { std::cout << "int" << std::endl; }
};
int main() {
// Leverage default argument to get the same syntax.
doFoo(1.0); // okay, prints "double".
// Handled by specializations defined later.
doFoo(MyClass{}); // okay, prints "MyClass".
doFoo(MyClassT<int>{}); // okay, prints "MyClassT<int>".
doFoo(42); // okay, prints "int".
// Pass in an explicit functor.
doFoo(my::Class{}, [](const auto&){});
return 0;
}
在编写模板化库时,有时希望在函数模板定义之后实现行为。例如,我正在考虑实现为
的记录器库中的log
函数
template< typename T >
void log(const T& t) {
std::cout << "[log] " << t << std::endl;
}
以后如果我想在我自己的类型上使用 log
,我会实现 std::ostream& operator<<(std::ostream&, CustomType)
并希望 log
函数自动在 CustomType
上运行。
但是,我很不确定这个模式是否符合标准。为了了解编译器如何处理它,我编写了以下最小示例。
#include<iostream>
// In some library...
void foo(double) { std::cout << "double" << std::endl; }
template< typename T>
void doFoo(T x) {
foo(x);
}
// In some codes using the library...
struct MyClass {};
template< typename T > struct MyClassT {};
namespace my { struct Class {}; }
void foo(MyClass) { std::cout << "MyClass" << std::endl; }
void foo(MyClassT<int>) { std::cout << "MyClassT<int>" << std::endl; }
void foo(my::Class) { std::cout << "my::Class" << std::endl; }
void foo(int) { std::cout << "int" << std::endl; }
int main() {
doFoo(1.0); // okay, prints "double".
doFoo(MyClass{}); // okay, prints "MyClass".
doFoo(MyClassT<int>{}); // okay, prints "MyClassT<int>".
doFoo(42); // not okay, prints "double". int seems to have been converted to double.
// doFoo(my::Class{}); // compile error, cannot convert my::Class to int.
return 0;
}
我希望通过重载 foo
函数注入到 doFoo
函数模板。结果看起来很不一致,因为它适用于自定义(模板化)类型,但不适用于命名空间或内置类型中的自定义类型。对于编译器 MSVC(与 Visual Studio 16.10.1 捆绑)以及 gcc 9.3.0.
我现在很困惑什么才是正确的行为。我猜这与实例化的位置有关。我的问题是:
- 以上代码合法吗?或者它们格式不正确?
- 如果代码是合法的,是什么导致了不同重载的不一致行为?
- 如果代码是非法的,什么是注入库模板的好替代方法? (我正在考虑将 functions/functors 显式传递给我的
doFoo
函数,就像<algorithm>
所做的那样。)
If the codes are illegal, what would be a good alternative to injecting library templates? (I'm thinking of passing functions/functors explicitly to my
doFoo
function, like what<algorithm>
is doing.)
您可以将仿函数与默认特征类型结合使用以获得“两全其美”。这基本上就是标准库中无序容器使用 std::hash
.
它允许通过专门化特征或显式传递仿函数来进行注入。
#include<iostream>
// In some library...
template <typename T>
struct lib_trait;
template<>
struct lib_trait<double> {
void operator()(double) const { std::cout << "double" << std::endl; }
};
template<typename T, typename CbT=lib_trait<T>>
void doFoo(T x, const CbT& cb={}) {
cb(x);
}
// In some codes using the library...
struct MyClass {};
template< typename T > struct MyClassT {};
namespace my { struct Class {}; }
template<>
struct lib_trait<MyClass> {
void operator()(MyClass) const { std::cout << "MyClass" << std::endl; }
};
template<>
struct lib_trait<MyClassT<int>> {
void operator()(MyClassT<int>) const { std::cout << "MyClassT<int>" << std::endl; }
};
template<>
struct lib_trait<int> {
void operator()(int) const { std::cout << "int" << std::endl; }
};
int main() {
// Leverage default argument to get the same syntax.
doFoo(1.0); // okay, prints "double".
// Handled by specializations defined later.
doFoo(MyClass{}); // okay, prints "MyClass".
doFoo(MyClassT<int>{}); // okay, prints "MyClassT<int>".
doFoo(42); // okay, prints "int".
// Pass in an explicit functor.
doFoo(my::Class{}, [](const auto&){});
return 0;
}