19

I was experimenting with using arbitrary functions in fold expressions, when I found the following code that compiles with gcc but does not compile with clang.

enum Enum {
    A = 3,
    B = 8,
    C = 5
};

namespace EnumMax {
    constexpr Enum operator>>=(const Enum left, const Enum right) {
        return left < right ? right : left;
    }
}

template<Enum ... enums>
constexpr Enum max() {
    using EnumMax::operator>>=;
    return (enums >>= ...);
}

constexpr Enum max_v = max<A, B, C>();

https://godbolt.org/z/-LOudM

It seems that clang does not consider the overloaded operator, but attempts to use the regular >>= operator in the fold expression.

However, if instead the fold expression is spelled out, clang does consider the overloaded operator and will compile just fine:

constexpr Enum maxExplicit() {
    using EnumMax::operator>>=;
    return (A >>= (B >>= C));
}

Is this a clang bug? Or is the spelled out equivalent of a fold expression not exactly equivalent?

Stack Danny
  • 7,754
  • 2
  • 26
  • 55
Mark
  • 1,016
  • 6
  • 10
  • 2
    For reference: MSVC compiles your code as well: https://godbolt.org/z/UXPiur – R2RT Jul 05 '19 at 07:11
  • 8
    Posting as comment because I am not 100% certain, but it does look like a bug, as the standard makes no special handling of operators in fold expressions. Might be worth opening a ticket on clang issue tracker. – spectras Jul 05 '19 at 08:59
  • 4
    Filed [42518](https://bugs.llvm.org/show_bug.cgi?id=42518), your reasoning is correct. – Barry Jul 05 '19 at 12:49

1 Answers1

3

Per [expr.prim.fold]/fold-operator:

fold-operator: one of

+   -   *   /   %   ^   &   |   <<   >> 
+=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=  =
==  !=  <   >   <=  >=  &&  ||  ,    .*   ->*

So >>= is a fold-operator.

Per [expr.prim.fold]/2:

An expression of the form (... op e) where op is a fold-operator is called a unary left fold. An expression of the form (e op ...) where op is a fold-operator is called a unary right fold. Unary left folds and unary right folds are collectively called unary folds. In a unary fold, the cast-expression shall contain an unexpanded pack ([temp.variadic]).

So (enums >>= ...) is a unary right fold.

Per [temp.variadic]/10:

The instantiation of a fold-expression produces:

  • [...]

  • E1 op (⋯ op (EN−1 op EN)) for a unary right fold,

  • [...]

In each case, op is the fold-operator, N is the number of elements in the pack expansion parameters, and each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element. [...]

Therefore, (enums >>= ...) is semantically equivalent to (A >>= (B >>= C)) when you instantiate it. So this is a bug in Clang.

L. F.
  • 19,445
  • 8
  • 48
  • 82