Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Classes with both template and non-template conversion operators in the condition of switch statement

The problem originally arose in this question. Consider the following code:

class Var
{
public:

    operator int () const
    { return 0; }

    template <typename T>
    operator T () const
    { return T(); }

};

int main()
{
    Var v;
    switch (v)
    { }
}

Without the operator int() const { return 0; }, both g++ and clang reject the code.

However, the above code, with the operator int(), is accepted by clang but rejected by g++ with the following error:

main.cpp:17:14: error: default type conversion can't deduce template argument for 'template<class T> Var::operator T() const'
     switch (v)
              ^

Which compiler is correct?

like image 355
T.C. Avatar asked Jul 30 '14 21:07

T.C.


4 Answers

I believe clang is correct here.

We can see from the draft C++ standard section 6.4.2 The switch statement that this involves a contextually implicit conversion. Paragraph 2 says (*emphasis mine going forward):

The condition shall be of integral type, enumeration type, or class type. If of class type, the condition is contextually implicitly converted (Clause 4) to an integral or enumeration type.

We can see the section we need to use is 4 Standard conversions and paragraph 5 covers these cases, it says:

Certain language constructs require conversion to a value having one of a specified set of types appropriate to the construct. An expression e of class type E appearing in such a context is said to be contextually implicitly converted to a specified type T and is well-formed if and only if e can be implicitly converted to a type T that is determined as follows: E is searched for conversion functions whose return type is cv T or reference to cv T such that T is allowed by the context. There shall be exactly one such T.

This does not reference section 8.5 which allows for overload resolution by specifically referring to section 13.3 without allowing overload resolution we can not use:

template <typename T>
operator T () const

and therefore there is no ambiguity.

Note this is different from paragraph 4 which covers bool conversions in contexts of if, while etc... and says (emphasis mine):

Certain language constructs require that an expression be converted to a Boolean value. An expression e appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the declaration bool t(e); is well-formed, for some invented temporary variable t (8.5).

which specifically allows for overload resolution and refers directly to section 13.3 which covers this. It makes sense that it is allowed since we have a specific destination type bool to convert to which we don't have in the switch case.

Why

We can figure this out by going looking at N3323: A Proposal to Tweak Certain C++ Contextual Conversions, v3 it covers this issue. It would be hard to quote the whole paper so I will attempt to quote enough of the context. It says:

The context in which a C++ expression appears often influences how the expression is evaluated, and therefore may impose requirements on the expression to ensure such evaluation is possible. [...]

In four cases, the FDIS (N3290) uses different language to specify an analogous contextdependent conversion. In those four contexts, when an operand is of class type, that type must have a “single non-explicit conversion function” to a suitable (context-specific) type. [...]

and includes:

[stmt.switch]/2: “The condition shall be of integral type, enumeration type, or of a class type for which a single non-explicit conversion function to integral or enumeration type exists (12.3).”

and says:

The principal issue, in each of the four contexts cited in the Introduction, seems to lie in their common helpful but very strict requirement that limits a class to only one conversion operator [...]

Another concern is the scope of the qualifier “single” in the current wording. Must there be but a single conversion function in the class, or may there be several so long as a single one is appropriate to the context?

The current language seems unclear on this point. It is also unclear whether a conversion operator that produces a reference to an appropriate type is an appropriate conversion operator. (A question on this point was posted to the Core reflector on 2011-02-21, but has gone unanswered as of this writing.) Current compiler practice seems to admit such operators, but the current language seems not to.

and proposes:

To address all these concerns, we recommend instead to use the proven approach typified by the term contextually converted to bool as defined in [conv]/3. We therefore propose a modest addition to [conv]/3 to define contextual conversion to other specified types, and then appeal to this new definition.

and the new language would be as follows;

Certain other language constructs require similar conversion, but to a value having one of a specified set of types appropriate to the construct. An expression e of class type E appearing in such a context is said to be contextually implicitly converted to a specified type T and is well-formed if and only if e can be implicitly converted to a type T that is determined as follows: E is searched for conversion functions whose return type is cv T or reference to cv T such that T is allowed by the context. There shall be exactly one such T.

Note N3486: C++ Editor's Report, October 2012 shows us when N3323 was incorporated in the draft standard.

Update

Filed a gcc bug report.

like image 139
Shafik Yaghmour Avatar answered Oct 19 '22 04:10

Shafik Yaghmour


6.4.2/2 The switch statement (emphasis mine)

The condition shall be of integral type, enumeration type, or of a class type for which a single non-explicit conversion function to integral or enumeration type exists (12.3). If the condition is of class type, the condition is converted by calling that conversion function, and the result of the conversion is used in place of the original condition for the remainder of this section.

So my interpretation is that g++ is correct here.

like image 9
iavr Avatar answered Oct 19 '22 02:10

iavr


I believe gcc is correct, but the standard is flawed.

gcc is correct because the standard mandates a single non-explicit conversion operator to integral or enumeration types for types used in switch.

The standard is wrong because detecting that case involves solving the halting problem.

operator T can have a SFINAE clause of arbitrary complexity attached to it. The compiler, under the standard, must determine if there is a T such that the T is an enum.

template<class...Ts>
struct evil {
  enum { bob = 3+sizeof...(Ts) };
};

struct test {
  operator int() const { return -1; };
  template<class T, typename std::enable_if<T::bob==2>::type* unused=nullptr>
  operator T() const { return T::bob; }
};
int main() {
  switch( test{} ) {
    case -1: std::cout << "int\n"; break;
    case 2: std::cout << "bob\n"; break;
    default: std::cout << "unexpected\n"; break;
  }
}

The above code demonstrates a case where we have an infinite number of enums implicitly available. We have an operator T that will cast to type T if and only if T::bob==2. Now, there are no such enums in our program (and even if we removed the 3+ there would still not be, because it is not an enum class -- easily rectified).

So test can only be converted to int, and as such the switch statement should compile. gcc fails this test, and claims that the template operator T makes it ambiguous (without telling us what T, naturally).

Replacing enum type with enum class type, and removing the 3+ makes the switch statement illegal under the standard. But for the compiler to figure that out, it basically has to instantiate all possible templates in the program looking for a secret enum with the property in question. With a bit of work, I can thus force the compiler to solve NP complete problems (or, excluding compiler limitations, the halting problem) in order to determine if a progrma should compile or not.

I do not know what the right wording should be. But the wording as written sure isn't sound.

like image 3
Yakk - Adam Nevraumont Avatar answered Oct 19 '22 03:10

Yakk - Adam Nevraumont


In my hummble opinion and based on §13.3.3/1 Best viable function [over.match.best], the non-template overloaded conversion operator (i.e., operator int() const) has a higher precedence in terms of overload resolution picking, than its template counterpart (i.e., template <typename T> operator T () const).

Thus, overloaded resolution would correctly choose operator int() const over template <typename T> operator T () const since is the best viable function.

Furthermore, and since the non-template version would be chosen over the template one (i.e., the template would not be materialized/qualified by the compiler), class Var would have a single conversion function and thus the requirement in §6.4.2/2 The switch statement [stmt.switch] for single integral conversion would be satisfied.

Consequently, Clang is right and GCC is wrong.

like image 2
101010 Avatar answered Oct 19 '22 03:10

101010