模板函数中具有单个可选参数和默认值的函数

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

打印 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::optionalhttps://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::variantstd::any。要限制绑定类型,您可以使用概念(这有点深,但请参阅例如 Boost Type Erasure)。


¹ 我真的真的真的很想转转啊