I would like to update conversion functions which have the following signatures:
template<typename In, typename Out>
bool Convert(const In& p_in, Out& p_out);
// Returns `true` if the conversion succeeded, `false` otherwise.
so that I could use std::optional
instead. Something like (notice template parameters were switched):
template<typename Out, typename In>
std::optional<Out> Convert(const In& p_in);
There is a big change as far as templates are concerned: One of the templated type is now used in the return type of Convert
. Because of this, compilation fails unless I specify function parameters. Example:
#include <optional>
class A
{
public:
A() = default;
A(int p_value)
: m_value(p_value)
{}
private:
int m_value = 0;
};
template<typename In, typename Out>
bool Convert(const In& p_in, Out& p_out)
{
return false;
}
template<>
bool Convert(const int& p_in, A& p_out)
{
p_out = A(p_in);
return true;
}
template<typename Out, typename In>
std::optional<Out> Convert2(const In& p_in)
{
return std::nullopt;
}
template<>
std::optional<A> Convert2(const int& p_in)
{
return A(p_in);
}
int main()
{
const int in = 2;
// Fine, but not ideal since we have `std::optional` available:
A out;
const bool result1 = Convert(in, out);
// Failed:
//const std::optional<A> result2 = Convert2(in);
// Ok, but sadly A is repeated at called site:
const std::optional<A> result3 = Convert2<A>(in);
return 0;
}
Here is the error message (gcc
) if I uncomment the second call:
<source>: In function 'int main()':
<source>:62:46: error: no matching function for call to 'Convert2(const int&)'
62 | const std::optional<A> result2 = Convert2(in);
| ~~~~~~~~^~~~
<source>:30:20: note: candidate: 'template<class Out, class In> std::optional<_Tp> Convert2(const In&)'
30 | std::optional<Out> Convert2(const In& p_in)
| ^~~~~~~~
<source>:30:20: note: template argument deduction/substitution failed:
<source>:62:46: note: couldn't deduce template parameter 'Out'
62 | const std::optional<A> result2 = Convert2(in);
| ~~~~~~~~^~~~
Using auto
, I was able to get this to work:
template<typename In>
auto Convert(const In& p_in)
{
return std::nullopt;
}
template<>
auto Convert(const int& p_in)
{
return std::make_optional<A>(p_in);
}
This has the advantage of being usable as I want at call site:
const std::optional<A> result4 = Convert3(in)
But I feel the function signature is now very much less informative on what the function does. One has to read the implementation to know a std::optional<A>
is returned.
Would there be a way to make this work, without having to specify templated types at call site?
template<typename Out, typename In>
std::optional<Out> Convert(const In& p_in)
{
// ?
}
template<>
std::optional<A> Convert(const int& p_in)
{
// ?
}
int main()
{
const std::optional<A> result = Convert(in)
return 0;
}
Sources on Godbolt : https://godbolt.org/z/bYjrcPsPx.
The natural way to update the code to use std::optional
would be:
template <typename In, typename Out>
bool OldConvert(const In& p_in, Out& p_out);
template <typename Out, typename In>
std::optional<Out> Convert(const In& p_in)
{
Out res;
if (OldConvert(p_in, res)) { // Possibly put code directly here
return std::make_optional(res);
} else {
return std::nullopt;
}
}
and usage changes from
A out;
if (Convert(in, out)) {
use(out);
}
to
if (const auto out = Convert<A>(in)) {
use(*out);
}
whereas auto
might be textually replaced by std::optional<A>
(if you want to be explicit) or std::optional
(to avoid repetition of A
)
That seems idiomatic to me.
You have another alternative with (ab)use of converting operator to have the syntax:
if (const std::optional<A> out = Converter(in)) {
use(*out);
}
with
template <typename In>
struct ConverterHelper
{
template <typename Out>
operator std::optional<Out>() && {
return Convert<Out>(arg);
}
const In& arg;
};
template <typename In>
auto Converter(const In& p_in)
{
return ConverterHelper{p_in};
}