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

发布于 2025-01-09 10:36:31 字数 475 浏览 0 评论 0原文

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

I want a function with has only 1 argument which is optional with generic type and has assigned boost::none as default value. Is that possible?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧伤慢歌 2025-01-16 10:36:31

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

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

或显式实例化它:

f<int>(12);

Yes, that's possible, but it doesn't work together with template deduction (f(12) will try to instantiate a f<int&&>, which doesn't exist). Your code will compile when you call it like that:

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

or explicitly instantiate it:

f<int>(12);
2025-01-16 10:36:31

嗯,另一个答案是关闭。但还没有完全实现。 f(12) 不会“尝试实例化 f”。事实上,它无法推导 T,因为 T 处于非推导上下文中。

另外,你的问题不是重点:即使没有默认值,你也会遇到同样的问题: 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");
}

现在,在我盲目地向您展示如何解决所有这些问题之前,请先问自己一个问题:我们在这里做什么

如果您想要默认参数,那么根据定义,这是否意味着它们不是可选值?也许您只需要: 编译器资源管理器

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

使用一些黑客技术,您可以通过默认模板类型参数来组合重载:

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

打印 编译器Explorer

value: 12
value: string
no argument

如果您需要可选<>怎么办

在这种情况下,在您的特定示例中,您可能需要可选避免不必要地复制所有参数;但请参阅引用类型的std::可选专业化

如果您真的真的想要

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

现在,您必须使模板参数成为推导的上下文,然后要确保...它是一个可选

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 联系起来>.)

相反,您可以使用 静态多态性。最简单的版本是我展示的第一个版本,使用函数重载。

如果您要模仿 C++ 中的动态类型接口,您可能会使用 std::variantstd::any 来代替。要限制绑定类型,您可以使用概念(这有点深,但请参阅“Boost Type Erasure”)。


1 我真的真的真的很想玩之字形啊

Mmm the other answer is close. But not quite there. f(12) doesn't "try to instantiate f<int&&>". In fact, it fails to deduce T because T is in non-deduced context.

Also, your question was beside the point: even without a default value you have the same problem: 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");
}

Now, before I blindly show you how you can fix all that, ask yourself the question: What are we doing here.

If you want default arguments, doesn't that by definition mean that they aren't optional values? Maybe you simply need: 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();
}

Printing

value: 12
value: string
no value

With some hackery you can combine the overloads by defaulting the template type argument:

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

Prints Compiler Explorer

value: 12
value: string
no argument

What If You Require optional<>

In that case, in you specific example you would probably want optional<T const&> to avoid needlessly copying all the arguments; but see std::optional specialization for reference types.

If You Really Really Want¹

Say, you MUST have the semantics you were looking for. You do not care that you won't be able to know the difference between calling with no argument vs. calling with an uninitialized optional (none). This is kinda like many scripting languages, right?

Now you have to make the template argument become deduced context, and then want to ensure that... it is an 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();
}

One "advantage" is that that now you clearly see the copying being done.

Another "advantage" is that now you can support std::optional the same way: https://godbolt.org/z/1Mhja83Wo

template <typename T> struct is_optional<std::optional<T>> : std::true_type { };

Summary

I hope this answer gets the point across that C++ is not a dynamically typed language. This implies that the idea of optional arguments of "unknown" type is really not idiomatic. (It might be a bit unfortunate that Boost called it boost::none instead of e.g. std::nullopt, perhaps giving people associations with Python's None.)

Instead, you can use static polymorphism. The simplest version of that was the first I showed, using function overloading.

If you were to mimic a dynamic type interface in C++, you would probably use std::variant or std::any instead. To restrict the bound types you would use concepts (this is getting a bit deep, but see e.g. Boost Type Erasure).


¹ i really really really wanna zig a zig ah

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文