I would like to write a template function taking an optional argument using std::optional,
template<typename T1, typename T2>
void fcn(T1 v1, std::optional<T2> v2)
{
}
int main()
{
fcn(1, 1); // failed to compile
fcn(1, std::nullopt); // failed as well
}
However, the compiler can't deduce T2 for neither cases.
I understand that there's a trick when T1 and T2 are the same by putting the second argument into a non-deducible context like,
template<typename T>
void fcn(T v1,
std::enable_if<true, T>::type v2)
{
}
So that the caller can call fcn(1, 1) normally. And my first question is how to achieve this for my previous example without forcing the caller to write fcn(v1, std::make_optional(v2))?
And my second question is how to call fcn using std::nullopt other than writing function overloads? Since in my use case there might be several optional arguments and writing all the permutation of overloads will be too impractical.
----EDIT----
As the comments suggest, there are multiple ways to make the code compile, but none is as intuitive as a normal function call. Maybe a further question to ask is what is the proper way to write template functions taking in optional arguments with different types?
how to achieve this for my previous example without forcing the caller to write fcn(v1, std::make_optional(v2))?
You can do it by separating the deduction part from the use of std::optional.
how to call fcn using std::nullopt other than writing function overloads?
You can add one templated overloading, which will deal with all std::nullopt cases. For this one, you need to specify the template argument for that optional. It's not a problem, because you can change the order of 2 template parameters.
template<typename T2, typename T1>
void fcn_impl(T1 v1, std::optional<T2> v2) {}
template<typename T2, typename T1>
void fcn(T1&& v1, T2&& v2)
{
fcn_impl(std::forward<T1>(v1),
std::make_optional(std::forward<T2>(v2)));
}
template<class T2, class T1>
void fcn(T1&& v1)
{
fcn_impl<T2>(std::forward<T1>(v1), std::nullopt);
}
int main()
{
// call the first one
fcn(1, 1);
// call std::nullopt one
fcn<short>(1);
}
And my first question is how to achieve this for my previous example without forcing the caller to write fcn(v1, std::make_optional(v2))
You might use extra indirection (as shown by liliscent), but for std::null_opt, you would have to specify the type...
As the comments suggest, there are multiple ways to make the code compile, but none is as intuitive as a normal function call. Maybe a further question to ask is what is the proper way to write template functions taking in optional arguments with different types?
One easy way is overloads:
template<typename T1, typename T2>
void fcn(T1 v1, T2 v2)
{
}
template<typename T1>
void fcn(T1 v1)
{
}
An alternative would be to check inside the function the given type
template<typename T1, typename T2, typename T3>
void fcn(T1 v1, T2 v2, T3 v3)
{
if constexpr (std::is_same<std::nullopt_t, T2>::value) {
// ...
}
if constexpr (std::is_same<std::nullopt_t, T3>::value) {
// ...
}
}
A more complicated way would be to use a variadic template with a parameter pack:
template<typename T1, typename ... Ts>
void fcn(T1 v1, Ts&&... args)
{
if constexpr (sizeof...(Ts) == 0) {
// ...
} else /*if constexpr (sizeof...(Ts) == 1)*/ {
auto& v2 = std::get<0>(std::tie(args...));
// ...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With