【问题标题】:Function with single optional parameter and default value in template function模板函数中具有单个可选参数和默认值的函数
【发布时间】:2022-02-23 21:21:59
【问题描述】:

我想要一个只有 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;
}

【问题讨论】:

  • 重载是一种替代方法:void f() { std::cout&lt;&lt;"v has no value!"; }.
  • 大声笑@Jarod42 显然我有too much time
  • @sehe:我忘了:void f(std::nullopt_t) { std::cout &lt;&lt; "no value\n";}.

标签: c++ templates boost c++14


【解决方案1】:

是的,这是可能的,但它不能与模板推导一起使用(f(12) 将尝试实例化一个不存在的f&lt;int&amp;&amp;&gt;)。当您这样调用它时,您的代码将编译:

f(boost::optional<int>{12});

或显式实例化它:

f<int>(12);

【讨论】:

【解决方案2】:

嗯,另一个答案是 close。但不完全在那里。 f(12) 不会“尝试实例化 f&lt;int&amp;&amp;&gt;”。事实上,它无法推导出 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&lt;&gt;

在这种情况下,在您的具体示例中,您可能希望optional&lt;T const&amp;&gt; 避免不必要地复制所有参数;但请参阅std::optional specialization for reference types

如果你真的想要¹

说,您必须具有您正在寻找的语义。您不在乎您将无法知道不带参数调用与使用未初始化的可选项调用之间的区别 (none)。这有点像许多脚本语言,对吧?

现在你必须让模板参数成为推断的上下文,然后要确保......它是一个optional&lt;T&gt;

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)。


¹ 我真的真的很想曲折啊

【讨论】:

    猜你喜欢
    • 2015-07-08
    • 1970-01-01
    • 1970-01-01
    • 2022-07-20
    • 2019-09-12
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多