模板函数中具有单个可选参数和默认值的函数
Function with single optional parameter and default value in template function
我想要一个只有 1 个参数的函数,该参数对于泛型类型是可选的,并且已指定 boost::none
作为默认值。这可能吗?
#include <iostream>
#include <string>
#include <boost/optional.hpp>
template <typename T>
void f(boost::optional<T> v = boost::none)
{
if (v)
{
std::cout<<"v has value: " << v.get();
}
else
{
std::cout<<"v has no value!";
}
}
int main()
{
f(12);
f("string");
return 0;
}
是的,这是可能的,但它不能与模板推导一起使用(f(12)
将尝试实例化 f<int&&>
,它不存在)。当您这样调用代码时,您的代码将编译:
f(boost::optional<int>{12});
或显式实例化它:
f<int>(12);
嗯,另一个答案是 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";
}
}
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)。
¹ 我真的真的真的很想转转啊
我想要一个只有 1 个参数的函数,该参数对于泛型类型是可选的,并且已指定 boost::none
作为默认值。这可能吗?
#include <iostream>
#include <string>
#include <boost/optional.hpp>
template <typename T>
void f(boost::optional<T> v = boost::none)
{
if (v)
{
std::cout<<"v has value: " << v.get();
}
else
{
std::cout<<"v has no value!";
}
}
int main()
{
f(12);
f("string");
return 0;
}
是的,这是可能的,但它不能与模板推导一起使用(f(12)
将尝试实例化 f<int&&>
,它不存在)。当您这样调用代码时,您的代码将编译:
f(boost::optional<int>{12});
或显式实例化它:
f<int>(12);
嗯,另一个答案是 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";
}
}
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)。
¹ 我真的真的真的很想转转啊