1

Why does the following define the ! and - operators, but fail to define the ~ operator?

#include <type_traits>
#include <cstdint>
#include <typeinfo>
#include <cstdio>

template <typename T, T v>
struct integral_constant
    : std::integral_constant<T, v>
{
};

#define DECL_UNARY_OP(op) \
    template <typename T, T t> \
    constexpr integral_constant<decltype(op t), (op t)> \
    operator op(integral_constant<T, t>) \
    { return {}; } \


DECL_UNARY_OP(~);
DECL_UNARY_OP(-);
DECL_UNARY_OP(!);

int main() {
    constexpr auto x = integral_constant<uint8_t, 1>{};
    constexpr auto y = integral_constant<uint8_t, 10>{};
    constexpr auto z = integral_constant<uint8_t, 100>{};
    puts(typeid(-x).name());  // integral_constant<...>
    puts(typeid(~y).name());  // int!
    puts(typeid(!z).name());  // integral_constant<...>
}

Compiling this on GCC-4.8.2 gives the following on godbolt, where you can clearly see that the middle operation has decayed away from an integral_constant type.

Why is this happening?

Eric
  • 95,302
  • 53
  • 242
  • 374
  • 3
    Why do you ask ? – Sid S Jun 19 '18 at 03:47
  • Upping the compiler version to gcc 6.1+ fixes it. It looks like you've either stumbled across UB or a now-patched compiler bug – Justin Jun 19 '18 at 04:15
  • 1
    My guess: this is a compiler bug with `~` which prefers the implicit conversion to a `uint8_t` then builtin `operator~`, rather than the function call to the newly defined `operator~` – Justin Jun 19 '18 at 04:22
  • All versions of Clang seem to do the right thing and not convert the result either. It seems like a bug indeed. On another note, I hope you are aware that your macro produces operators that promote integer types to `int`. – StoryTeller - Unslander Monica Jun 19 '18 at 06:06
  • @Sid: Because I want it to not happen, and I'm stuck with that compiler version! – Eric Jun 19 '18 at 06:33
  • @StoryTeller: Yep, I'm aware that the result would end up `std::integral_constant` – Eric Jun 19 '18 at 06:33

1 Answers1

1

A simpler reproduction can be obtained by eliminating std::integral_constant:

#include <cstdint>
#include <typeinfo>
#include <cstdio>

template <typename T, T v>
struct integral_constant { };

template <typename T, T t>
constexpr integral_constant<decltype(~t), (~t)>
operator ~(integral_constant<T, t>) { return {}; }


int main() {
    constexpr auto y = integral_constant<uint8_t, 10>{};
    puts(typeid(~y).name());
}

Which now fails to compile with:

<source>: In function 'int main()':
<source>:17:17: error: no match for 'operator~' (operand type is 'const integral_constant<unsigned char, 10u>')
     puts(typeid(~y).name());
                 ^
<source>:17:17: note: candidate is:
<source>:11:1: note: template<class T, T t> constexpr integral_constant<decltype (~ t), (~ t)> operator~(integral_constant<T, t>)
 operator ~(integral_constant<T, t>)
 ^
<source>:11:1: note:   template argument deduction/substitution failed:
<source>: In substitution of 'template<class T, T t> constexpr integral_constant<decltype (~ t), (~ t)> operator~(integral_constant<T, t>) [with T = unsigned char; T t = 10u]':
<source>:17:18:   required from here
<source>:11:1: error: 't' was not declared in this scope
Compiler returned: 1

It seems this can be fixed by adding extra parentheses,

-integral_constant<decltype(~t), (~t)>
+integral_constant<decltype((~t)), (~t)>
Eric
  • 95,302
  • 53
  • 242
  • 374