26

A class in C++ can define one or several conversion operators. Some of them can be with auto-deduction of resulting type: operator auto. And all compilers allow the programmer to mark any operator as deleted, and operator auto as well. For concrete type the deletion means that an attempt to call such conversion will result in compilation error. But what could be the purpose of operator auto() = delete?

Consider an example:

struct A {
    operator auto() = delete;
};

struct B : A { 
    operator auto() { return 1; }
};

int main() {
    B b;
    A a = b;   // error in Clang
    int i = b; // error in Clang and GCC
    int j = a; // error in Clang and GCC and MSVC
}

Since the compiler is unable to deduce the resulting type, it actually prohibits any conversion from this class or from derived classes with the error:

function 'operator auto' with deduced return type cannot be used before it is defined.

Demo: https://gcc.godbolt.org/z/zz77M5zsx

Side note that compilers slightly diverge in what conversions are still allowed (e.g. GCC and MSVC permit the conversion to base class), which one of them is right here?

Fedor
  • 17,146
  • 13
  • 40
  • 131
  • for clarification, did you expect any of the conversions in your code to not be an error? – 463035818_is_not_an_ai Sep 15 '21 at 13:38
  • 7
    To clarify, not everything that is permitted is necessarily useful. Some things are permitted as a consequence of other rules but don't have any practical applications. In this case, I'm not sure whether or not there is any useful application. But it is worth noting that being permitted doesn't imply usefulness. – François Andrieux Sep 15 '21 at 13:40

1 Answers1

8

But what could be the purpose of operator auto() = delete?

What would be the purpose of the following function?

auto f() = delete;

As per the grammar functions, [dcl.fct.def.general]/1, the function-body of a function-definition may be = delete; e.g., it is syntactically valid to define a function as deleted.

C++14 introduced auto return type deduction for functions, and given the allowed grammar for function definitions, as per C++14 the grammar allows explicitly-deleting a function with auto return type.

Whether this corner case is useful or not is not really for the language ponder about, as there is always a cost of introducing corner cases (e.g. "the grammar for definitions of functions shall have a special case for auto type deduction"). Whilst there are limitations on where one may provide explicitly-defaulted function definitions ([dcl.fct.def.default]), the same restrictions do not apply for explicitly-deleted function definitions ([dcl.fct.def.delete]).


which one of them is right here?

A a = b;   // error in Clang

Clang is arguable wrong to pick the user-defined conversion function for this initialization. As per [dcl.init.general]/15.6, /15.6.2:

Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. [...]

takes precedence over /15.6.3:

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution ([over.match]). [...]


As a user of the language, providing a deleted definition of a function with auto return type could be used to semantically mark that no one should provide any kind of overload of a given function name.

auto f() = delete;  // never expose a valid overload for f()

// elsewhere:
int f() { return 42; }
  // error: functions that differ only in 
  //        their return type cannot be overloaded

For the special case of user-defined conversion operators, an explicitly-default auto return type user-defined conversion function can be used e.g. in a intended-for-composition base class, similar to Scott Meyers C++03 trick of making a class non-copyable (before C++11 introduced = delete).

struct NoUserDefinedConversionFunctionsAllowed {
    operator auto() = delete;
};

struct S : NoUserDefinedConversionFunctionsAllowed { 
    operator int() { return 1; }  // can never be used
};
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Inheriting from base classes with `= delete` is only useful for special member functions. Normal functions merely hide the base class version. `struct S : NoUserDefinedConversionFunctionsAllowed` is just as capable of defining usable conversion functions as `struct S`. – Ben Voigt Sep 15 '21 at 15:15
  • @BenVoigt Are you sure? E.g. the operator int() is S above is not invocable due to the inherited blanket-deduction-failure operator auto(), afaict. – dfrib Sep 15 '21 at 21:31
  • Why would it be inherited? – Ben Voigt Sep 15 '21 at 21:35
  • @BenVoigt Have you tried compiling the example? Afaict even if inherited privately it will be part of the lookup set, and as return type deduction fails when trying consider if it is a viable candidate, you get a hard error. – dfrib Sep 15 '21 at 21:37
  • @BenVoigt That example is neither operator auto() nor return type deduction. – dfrib Sep 15 '21 at 21:38
  • I thought this was being proposed as a syntax for implicit template, like `auto` in lambdas.... ? So I wrote the long syntax that is supported by the current language version. – Ben Voigt Sep 15 '21 at 21:40
  • @BenVoigt auto is quite overloaded at this point. You may be referring to abbreviated function templates, which cover short-hand invented template parameters for auto specified function parameter. Comparable to generic lambas. auto return type deduction is a different topic. – dfrib Sep 15 '21 at 21:42
  • In any case it *shouldn't* be part of the lookup set, since lookup doesn't recurse to base classes once a match is found in a more derived class (unless the base class overloads are introduced via `using`). But conversion operators don't all have the same name, so the hiding doesn't happen. Weird. – Ben Voigt Sep 15 '21 at 21:42
  • @BenVoigt Again: are you sure? Or are you in fact referring to shadowed names in derived classes? The blanket operator auto() _could_ be a different and better match than any of the derived class’ user defined conversion functions. Thus it needs to be inspected and deduced for the overload context(/call), and the resulting deduction failure is not SFINAE but a hard error. – dfrib Sep 15 '21 at 21:47
  • So... a note explicitly mentions that the hiding doesn't happen: https://eel.is/c++draft/class.conv.fct#4 I'm not sure I like that choice but it is supported by the current language. – Ben Voigt Sep 15 '21 at 21:48
  • @BenVoigt I think the normative text is class.member.lookup/4 and /5. But yes it does open up to allow this oddity, and the last part of my answer, ”how could this be used”, is indeed a tangent and contrived. However, afaict, supported by the standard (LL questions do tend to dwell into this oddities). – dfrib Sep 15 '21 at 21:57
  • Here's a way that the "can never be used" function can indeed be called: https://rextester.com/EMOW14867 class.member.lookup/4 does indeed terminate the search without checking the base classes, because there's a match in the derived class. It's some other rule, specific to conversion without naming a function, that is kicking in. – Ben Voigt Sep 15 '21 at 22:00
  • And here's an example where the derived class will always be better for any type (because it's templated it is always a perfect match) and the compilation fails anyway: https://rextester.com/UQVTD88545 – Ben Voigt Sep 15 '21 at 22:07
  • @BenVoigt Yeah for when using the exotic explicit operator call syntax, overload resolution is not longer resolving a conversion/initialization sequence (for the user defined conversion operator step), but rather finding a match for an explicit (operator) function call (a named function, if you will), in which case [class.member.lookup] applies and the base class will never be considered if the name is already found in the derived class. The second example with the template conversion function in the derived class is again overload resolution towards a conversion sequence/initialization, ... – dfrib Sep 16 '21 at 08:35
  • ... and overload resolution will need to enumerate candidates before ranking them, meaning it will visit the base class `operator auto()` and run into the hard deduction error. – dfrib Sep 16 '21 at 08:35