This pattern is usually used to deduce a function template
argument from the type its result is assigned to.
Here's my usecase. I have a function that reads a field from a
config file:
template <typename T> T
getConfigField(std::string_view name);
int x =
getConfigField<int>("field_x");
std::optional<int> y =
getConfigField<std::optional<int>>("field_y");
And let's say I don't want to repeat the type name twice. (In this specific case I could use auto, but it's not always an option, e.g. when passing getConfigField<..>(..) as a function argument or when initializing a non-static data member with it.)
One solution is to add a second non-template overload of getConfigField, which returns a proxy class with a templated conversion operator, like the one I've shown in the previous message:
struct Proxy {
std::string_view name;
template <typename T> operator T() const {return
getConfigField<T>(name);}
};
Proxy getConfigField(std::string_view name) {return
Proxy{name};}
int x = getConfigField("field_x"); //
T is deduced as int
void foo(int x);
foo(getConfigField("field_x"));
This works correctly for most types, std::optional is only outlier I've seen.
> Why is it a problem to convert to int first and then wrap that in an optional?
Because converting to int directly doesn't give you the chance to return a null optional.
In this example, imagine that getConfigField<std::optional<int>>("foo") returns null if given a non-existing field, while getConfigField<int>("foo") throws. But if I do std::optional<int> x( getConfigField("foo") ); for an non-existing field, it unexpectedly throws too, since T=int.
On Thu, 7 Dec 2023, 05:15 Egor via Std-Proposals, <std-proposals@lists.isocpp.org> wrote:
Hello!
std::optional<T> has a really greedy converting constructor. It accepts
almost any type that T is constructible from, and constructs a T from
it, even if that type is also convertible to optional<T>. Example:
https://gcc.godbolt.org/z/ncsYehW19
#include <iostream>
#include <optional>
#include <source_location>
struct A
{
template <typename T>
operator T()
{
std::cout <<
std::source_location::current().function_name() << '\n';
return {};
}
};
int main()
{
std::optional<int> x(A{}); // (1) Calls `operator int`, not
`operator optional<int>`.
// std::optional<int> y = A{}; // (2) Ambiguous.
x = A{}; // (3) Calls `operator int`, not `operator optional<int>`.
}
What's the practical use case for this type's conversion operator? If it can convert to both int and optional int, what does that mean? Why is it a problem to convert to int first and then wrap that in an optional? Does your type have a special way to construct an optional that is more efficient than converting to an int and then setting optional<int> from that?
"I find this confusing" is not very strong motivation.
This is very confusing in my eyes. std::optional is first type I see in
the wild that doesn't play nice with templated conversion operators.
I suggest we make this constructor of optional not participate in
overload resolution if the argument is convertible to optional<T>.
This would make (2) valid, and make all three lines call A::operator
optional<T>.
--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals