Classes with Both Template and Non-Template Conversion Operators in the Condition of Switch Statement

Internal compiler error - Templated conversion operator in switch expression

The compiler crashing is always a bug, this code does not compile on either gcc or clang but both provide an error without crashing. For clang the error is:

error: statement requires expression of integer type ('Var' invalid)
switch (v)
^ ~

gcc provides the following error:

error: ambiguous default type conversion from 'Var'
switch (v)
^

Also, note that flowing off the end of a value returning function is undefined behavior in C++.

Update

Adding:

operator int () const
{ return 0; }

to the class brings about different results from clang and gcc.

See Classes with both template and non-template conversion operators in the condition of switch statement for a discussion on whether gcc or clang is correct. My interpretation of N3323 implies clang is correct on this one.

Filed bug report

I filed a bug report for this ICE, so far no response. Even though this seems like an odd corner case it does cause an internal compiler error which should be fixed.

implicit conversion from class to enumeration type in switch conditional

This is a difference between C++11 and C++14; clang correctly accepts it in C++14 mode (-std=c++1y) and rejects it in C++11 mode (-std=c++11), while gcc is incorrect to accept it in C++11 mode.

The behavior of switch statements was changed by paper n3323, which landed after the C++11 standard was finalized.

[stmt.switch], in C++11:

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). [...]

In n3936 (wording per n3323):

2 - 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.

Contextual implicit conversion is a variant of implicit conversion (i.e. the declaration T t = e is required to be well-formed); for contextual implicit conversion to be well-formed the class type E is allowed to have multiple conversion functions, but all those valid in the context must have the same return type modulo cv and reference qualification: [conv]

5 - [...] 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.

In a switch statement, contextual implicit conversion is to an integral or enumeration type, so C must have at least one non-explicit conversion function to cv integral or enumeration type or reference to cv integral or enumeration type, and all its conversion functions to cv integral or enumeration type or reference to cv integral or enumeration type must have that same underlying type.

A pretty nice workaround (as mentioned in n3323) is to use unary plus to coerce the argument of the switch statement to arithmetic type:

  switch (+c) {
// ...

c++ implicit conversion on user-defined operator for template classes

I tried to edit Barrys answer with the following (runnable) code, that produces the correct output, but it was rejected there.

I'll add it here in case anyone else is curious.

#include <iostream>

template <int x>
struct A {
int a;
friend int operator+(A a, int b) { return a.a + b; }
};

template <int x>
struct B {
int b;
operator A<x>() { return {b+10}; }
friend int operator+(A<x>, int );
};

int main() {
std::cout << (A<12>{9} + 10) << std::endl;
std::cout << (B<12>{9} + 10) << std::endl;
}

Which prints

19
29

Is There Anything Like a Templatized Case-Statement

I had to do something like this once so I wrote a small wrapper to acheive the result neatly. You could use it as follows (see here for a test)

template<class T>
typename static_switch<sizeof(T)
,int // default case
,static_case<sizeof(char),char>
,static_case<sizeof(short),short>
,static_case<sizeof(long),long>
>::type foo(T bar){ ... }

Behind the scenes it pretty much does what you already have but by wrapping it we keep it (more) readable. There is also a version to allow you to switch direclty on the type T if you needed that.

Edit: At @Deduplicator's suggestion here is the code behind it

#include <type_traits>  

/*
* Select a type based on the value of a compile-time constant such as a
* constexpr or #define using static_switch.
*/

template<int I,class T>
struct static_case {
static constexpr int value = I;
using type = T;
};

template<int I, class DefaultType, class Case1, class... OtherCases>
struct static_switch{
using type = typename std::conditional< I==Case1::value ,
typename Case1::type,
typename static_switch<I,DefaultType,OtherCases...>::type
>::type;
};

struct fail_on_default {};

template<int I, class DefaultType, class LastCase>
struct static_switch<I,DefaultType,LastCase> {
using type = typename std::conditional< I==LastCase::value ,
typename LastCase::type,
DefaultType
>::type;

static_assert(!(std::is_same<type, fail_on_default>::value),
"Default case reached in static_switch!");
};

Automatic conversion of function arguments between related template classes

Template argument deduction requires exact matches (as Xeo points out in the comments, a single standard conversion sequence (Clause 4) will be applied, if needed), and user defined conversions are not considered. So it fails to deduce the template argument a from the second argument to f() (which is of type foo<1,2>). One way around this is to turn the second parameter type into a non-deduced context. Then a will be deduced from the first argument alone, and your code will compile.

#include <functional>
#include <memory>

template<typename T>
struct identity
{
using type = T;
};

template<int a> struct bar;

template<int a, int b> struct foo {
operator bar<a> const (); // operator-based conversion
};

template<int a> struct bar : public foo<a, a> {
bar() { }
template<int b> bar(const foo<a, b>&) { } // constructor-based conversion
};

template<int a, int b> foo<a, b>::operator bar<a> const () { return bar<a>(); }

template<int a> void f(bar<a> x, typename identity<bar<a>>::type y) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
int main() {
bar<1> x;
foo<1, 2> y;
f(x, y);
}

Live demo

Bug in Clang-12? case value is not a constant expression

Looks like a bug to me. Modifying case A to case nullptr gives the following error message (on the template definition):

error: no viable conversion from 'std::nullptr_t' to 'std::atomic<X>'

Making the class template into a class as suggested in the question then gives

error: value of type 'std::nullptr_t' is not implicitly convertible to 'int'

The latter message is correct. When class types are used in a switch condition they should be contextually implicitly converted to a integral or enumeration type and then be promoted. Here it should use std::atomic<X>'s conversion function which converts to X and is then promoted to int.

It shouldn't matter whether the statement appears in a template or not.

The base class is not dependent, so referring to the member x directly without this-> is also fine.

A conversion of A to std::atomic<X> would not be a constant expression, which is probably where the diagnostic comes from.

Here is an open Clang bug report and here is another potentially related bug report.

Actually, although the two mentioned bug reports are still open, the test code they present seems to work correctly since Clang 10. It seems to me that the variant in your question using a base class is a special case missed by that fix.



Related Topics



Leave a reply



Submit