18

§5.19/3 in C++14 defines an integral constant expression and a converted constant expression:

An integral constant expression is an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression. [ Note: Such expressions may be used as array bounds (8.3.4, 5.3.4), as bit-field lengths (9.6), as enumerator initializers if the underlying type is not fixed (7.2), and as alignments (7.6.2). —end note ] A converted constant expression of type T is an expression, implicitly converted to a prvalue of type T, where the converted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4). [ Note: such expressions may be used in new expressions (5.3.4), as case expressions (6.4.2), as enumerator initializers if the underlying type is fixed (7.2), as array bounds (8.3.4), and as integral or enumeration non-type template arguments (14.3). —end note ]

Maybe I'm missing something, but my first impression is that every integral constant expression is a converted constant expression.

Edit

And I also believe there is an error in this paragraph:

Instead of:

A converted constant expression of type T is an expression, implicitly converted to a prvalue of type T, ...

it should be:

A converted constant expression of type T is an expression, implicitly converted to a prvalue of an integral type, ...

And this change allows the following code to compile:

#include <iostream>
struct A { operator int() { return 5; } } a;

int main() {
    int b[a]{ 0, 1, 2, 3, 4 };
    std::cout << b[4] << '\n';
}

where a in the declaration int b[a]{ 0, 1, 2, 3, 4}; is a converted constant expression of type A, implicitly converted to a prvalue of integral type (int) where the converted expression 5 is a core constant expression, and the implicit conversion sequence contains only a user-defined conversion.

Ayrosa
  • 3,385
  • 1
  • 18
  • 29
  • 1
    And conversely *not* every `converted constant expression` is an `integral constant expression`? – Mark B Dec 18 '15 at 17:07
  • 2
    "A converted constant expression of type T is an expression, implicitly converted to a prvalue of an integral type," That's blatantly wrong. – T.C. Dec 19 '15 at 01:06
  • Note that the standard always use "a converted constant expression of `std::size_t`" or "a converted constant expression of the type of the _template-parameter_", it never says "a converted constant expression, period." – cpplearner Dec 19 '15 at 05:38

3 Answers3

11

Both definitions are needed because there are things you can do with one but not the other. And no, not every integral constant expression is really a converted constant expression. For the obvious example, a converted constant expression prohibits narrowing conversions, but an integral constant expression doesn't.

Therefore I can't do this:

enum x : char { a = 1024 };

If, however the initializer for an enum allowed an integral constant expression, rather than a converted constant expression, precisely that would be allowed.

As a Venn diagram, I'd draw the situation something like this:

enter image description here

So, there is quite a bit of overlap between the two (probably more than this diagram implies) but each allows at least a few things the other doesn't. I've given an example of one item in each direction, but haven't tried to list the differences exhaustively.

I'm not entirely convinced about user-defined conversions being prohibited for integral constant expressions though (and a quick test shows that the compilers I have handy at the moment allow them). That would give the situation as I originally wrote this answer, which would be more like this:

enter image description here

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • How come a converted constant expression is more restrictive than an integral constant expression? A converted constant expression applies to any type T with a user-defined conversion, which clearly are not applied to an integral constant expression, which are restricted to integral or enumeration types. – Ayrosa Dec 18 '15 at 17:23
  • @Ayrosa: I think of lack of narrowing conversions as a "bigger" restriction than lack of user-defined conversions, but ultimately you're right that that section the answer was probably misleading; I've replaced it with a Venn diagram that (I think) portrays the situation more accurately. – Jerry Coffin Dec 18 '15 at 17:32
  • @JerryCoffin But why is a user-defined conversion not allowed for an integral constant expression? – Eugene Zavidovsky Dec 18 '15 at 17:36
  • @Ayrosa You may use any conversion function (there is no constructors for integral types, oops :) to a core constant expression, which is intended to be an integral constant expression. For converted constant expressions you have those additional restrictions in the rule. – Eugene Zavidovsky Dec 18 '15 at 17:41
  • 1
    @EugeneZavidovsky: At least as I read the passage, the definition of `integral constant expression` is basically saying it has to have the form: `integral expression` -> `allowed conversion` -> `core constant expression`. If that's correct, a user-defined conversion can't apply, because it can only apply to a user defined type. A converted constant expression places restrictions on the conversion sequence and the result, so it's more like: `whatever` -> `allowed conversion` -> `core constant expression`. – Jerry Coffin Dec 18 '15 at 17:43
  • @Jerry Coffin I just thought about something like this `class A { constexpr operator int() { return 42; } }; enum { intgrl = A{} }; ` – Eugene Zavidovsky Dec 18 '15 at 17:47
  • 1
    @EugeneZavidovsky: As given that'll fail (but if you make `operator int()` public, at least g++ accepts it). I'm not entirely certain whether it *should* be allowed or not though. – Jerry Coffin Dec 18 '15 at 17:54
  • @JerryCoffin See my **Edit** above – Ayrosa Dec 18 '15 at 18:50
  • @JerryCoffin Hey! Look at the comments to the answer below. It seems, I was wrong about user-defined conversions for integral constant expressions. – Eugene Zavidovsky Jan 03 '16 at 17:58
4

Note: this answer is based on the latest draft standard for now, known as N4567. Some differences between it and the C++11/14 standard are pointed out.

integral constant expression and converted constant expression are different when class types are concerned. In C++98/03, when class types could not be used here (because there were no constexpr conversion functions at that time), there was indeed no such term as converted constant expression of type T.

For an integral constant expression, the destination type is unknown. But for a converted constant expression of type T, the destination type is known to be T, and T is not necessarily an integral or unscoped enumeration type1.

So, in order to compile a integral constant expression, the compiler first need to decide what the destination type is. If the expression has integral or unscoped enumeration type, then obviously the destination type is just the type of the expression. Otherwise, if the expression has a literal class type (let's call this type E), then the following process is used2:

The compiler examines all the non-explicit conversion functions in E3. Let's say the result types of these functions forms a set S. If S contains exactly one integral or unscoped enumeration type (reference modifier is stripped and const and volatile qualifiers are ignored: const volatile int& is considered as int in this process), then the destination type is just that type. Otherwise, the determination fails.

(It is important to note that in the aforementioned process, conversion function templates are not examined. )

As a consequence, for example, if a class type has two conversion functions, one is constexpr operator int and the other is constexpr operator long, then this type cannot be used in a integral constant expression (the destination type is undecidable). However, such type may be used in a converted constant expression of type int or in a converted constant expression of type long.

After deciding the destination type D, then overload resolution is applied to find the most appropriate conversion function or function template, and then the choosen conversion function (which must be constexpr) is called to produce a value of type D. — This part is, more or less, the same as a converted constant expression of type D.

In the following example, Var{} is a valid integral constant expression, but is an invalid converted constant expression of type std::size_t (the example is inspired by this question).

class Var
{
public:

    constexpr operator int ()
    { return 42; }

    template <typename T>
    constexpr operator T () = delete;
};

enum {
    x = Var{} // the initializer of `x` is expected to be an 
              // integral constant expression
              // x has value 42
};

int t[ Var{} ]; // the array bound is expected to be a
                // converted constant expression of type std::size_t
                // this declaration is ill-formed

References

N4567 5.20 [expr.const]p7

If an expression of literal class type is used in a context where an integral constant expression is required, then that expression is contextually implicitly converted (Clause 4) to an integral or unscoped enumeration type and the selected conversion function shall be constexpr.

N4567 4[conv]p5

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 non-explicit 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.

Notes

  1. In C++11/14, a converted constant expression could only be of integral or enumeration type. N4268 changes that.
  2. In C++11, there were no such process, instead, it is required that "the class type shall have a single non-explicit conversion function to an integral or enumeration type and that conversion function shall be constexpr." N3323 changed that to the current wording.
  3. The word "non-explicit" did not exist in the C++14 standard. It was added by CWG 1981.
Community
  • 1
  • 1
cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • But why do conversion function templates not participate in overload resolution in that context? You know, there is 13.3.1\7. – Eugene Zavidovsky Jan 03 '16 at 16:59
  • 1
    @EugeneZavidovsky They do participate in overload resolution AFAICS. They just don't participate in the process of deciding the destination type. Actually, conversion function templates can still be selected in overload resolution. – cpplearner Jan 03 '16 at 17:24
  • Oh... It is related to my conversation above. But do you know what? I think, I misunderstood those words in 5.20\3: "implicitly converted to a prvalue". They implied **the glvalue-to-prvalue conversion** and not any other (user-defined) conversions, because otherwise 7.2\5.1, 5.3, 7 would not make sense. So JerryCoffin was right in the comments to his answer... It looks like another compiler bug. **There should not be any process of deciding the destination type**, you have suggested! The destination type should always be specified. – Eugene Zavidovsky Jan 03 '16 at 17:57
  • 1
    @EugeneZavidovsky See 5.20\7. – cpplearner Jan 03 '16 at 23:39
  • Oh... So because of 5.20\7; 4\5 there may be no compiler bug then. But notice, that destination type must be specified anyway, if I understand it right. In that example it should be `std::size_t`, no? – Eugene Zavidovsky Jan 04 '16 at 09:20
0

After discussion of answers provided by Jerry Coffin and cpplearner, let me propose to rewrite those damn rules, like this:

[expr.const] 5.20\3 (modified)

An integral constant expression is an expression of integral or unscoped enumeration type, that is implicitly converted to a prvalue core constant expression of the same type such that the implicit conversion sequence contains only an lvalue-to-rvalue conversion.

[expr.const] 5.20\4 (modified)

A converted constant expression of type T is an expression of any type, that is implicitly converted to a constant expression of type T such that the implicit conversion sequence contains only:

  • user-defined conversions,
  • lvalue-to-rvalue conversions,
  • array-to-pointer conversions,
  • function-to-pointer conversions,
  • qualification conversions,
  • integral promotions,
  • integral conversions other than narrowing conversions,
  • null pointer conversions from std::nullptr_t,
  • null member pointer conversions from std::nullptr_t, and
  • function pointer conversions,

and where the reference binding (if any) binds directly. [ Note: such expressions may be used in new expressions, as case expressions, as enumerator initializers if the underlying type is fixed, as array bounds, and as non-type template arguments. — end note ]

Now the difference is obvious, uh? Also it should be reminded that according to 5.20\7; 4\5 an expression of literal class type may be used instead of integral constant expression in some cases.