C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Making the converting constructor of std::optional less greedy

From: Egor <holyblckcat_at_[hidden]>
Date: Thu, 7 Dec 2023 18:47:03 +0600
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.

07.12.2023 17:31, Jonathan Wakely пишет:
>
>
> On Thu, 7 Dec 2023, 05:15 Egor via Std-Proposals,
> <std-proposals_at_[hidden]> 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_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2023-12-07 12:47:07