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;
}
CodePudding user response:
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);
CodePudding user response:
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
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. Why not combine the ideas:
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
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