嗯,另一个答案是 close。但不完全在那里。 f(12) 不会“尝试实例化 f<int&&>”。事实上,它无法推导出 T,因为 T 在 non-deduced context 中。
另外,您的问题是题外话:即使没有默认值,您也会遇到同样的问题:Compiler Explorer
template <typename T> void f(boost::optional<T> v) {
if (v) {
std::cout<<"value: " << v.get() << "\n";
} else {
std::cout<<"no value\n";
}
}
int main()
{
f(12);
f("string");
}
现在,在我盲目地向您展示如何解决所有这些问题之前,请先问自己一个问题:我们在这里做什么。
如果您想要默认参数,那么根据定义,这是否意味着它们不是可选值?也许你只需要:Compiler Explorer
template <typename T> void f(T const& v) {
std::cout << "value: " << v << "\n";
}
void f() {
std::cout << "no value\n";
}
int main()
{
f(12);
f("string");
f();
}
打印
value: 12
value: string
no value
通过一些hackery,您可以通过默认模板类型参数来组合重载:
template <typename T = struct not_given*> void f(T const& v = {}) {
if constexpr(std::is_same_v<T, not_given*>) {
std::cout << "no argument\n";
} else {
std::cout << "value: " << v << "\n";
}
}
打印Compiler Explorer
value: 12
value: string
no argument
如果你需要optional<>
在这种情况下,在您的具体示例中,您可能希望optional<T const&> 避免不必要地复制所有参数;但请参阅std::optional specialization for reference types。
如果你真的想要¹
说,您必须具有您正在寻找的语义。您不在乎您将无法知道不带参数调用与使用未初始化的可选项调用之间的区别 (none)。这有点像许多脚本语言,对吧?
现在你必须让模板参数成为推断的上下文,然后要确保......它是一个optional<T>:
template <typename T, typename = void> struct is_optional : std::false_type { };
template <typename T> struct is_optional<boost::optional<T>> : std::true_type { };
template <typename T = boost::optional<void*> >
std::enable_if_t<is_optional<T>::value> f(T const& v = {}) {
if (v) {
std::cout << "value: " << *v << "\n";
} else {
std::cout << "no value\n";
}
}
template <typename T>
std::enable_if_t<not is_optional<T>::value> f(T const& v) {
return f(boost::make_optional(v));
}
int main()
{
f(12);
f("string");
f();
}
一个“优势”是现在您可以清楚地看到正在完成的复制。
另一个“优势”是现在你可以用同样的方式支持std::optional:https://godbolt.org/z/1Mhja83Wo
template <typename T> struct is_optional<std::optional<T>> : std::true_type { };
总结
我希望这个答案能说明 C++ 不是动态类型语言。这意味着“未知”类型的可选参数的想法确实不是惯用的。 (不幸的是,Boost 将其称为 boost::none 而不是例如 std::nullopt,这可能会让人们联想到 Python 的 None。)
相反,您可以使用static polymorphism。最简单的版本是我展示的第一个版本,使用函数重载。
如果您要在 C++ 中模拟动态类型接口,您可能会改用 std::variant 或 std::any。要限制您将使用概念的绑定类型(这有点深,但请参阅例如 Boost Type Erasure)。
¹ 我真的真的很想曲折啊