Home > OS >  Function with single optional parameter and default value in template function
Function with single optional parameter and default value in template function

Time:02-23

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

  • Related