2

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;
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
John Carl
  • 23
  • 4

2 Answers2

2

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);
lubgr
  • 37,368
  • 3
  • 66
  • 117
  • I'm not sure this leads to better code or even got to the heart of the problem. I [added some thoughts of my own](https://stackoverflow.com/a/71236368/85371) – sehe Feb 23 '22 at 11:48
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

sehe
  • 374,641
  • 47
  • 450
  • 633