4

I'm trying to define constexpr friend operator in template. I got a compiler error when trying to instantiate this operator in non-constexpr context. If I define the very same operator as a template class member of as a free template function, it works fine.

template <typename T>
struct A
{
    T Value;

    // error
    friend constexpr bool operator== (const A& l, const A& r)
    {
        return l.Value == r.Value;
    }

    // ok
    constexpr bool operator!= (const A& r) const
    {
        return Value != r.Value;
    }
};

// ok
template <typename T> constexpr bool
operator< (const A<T>& l, const A<T>& r)
{
    return l.Value < r.Value;
}

#include <string>
int main ()
{
    A<std::string> s;
    bool ret = (s < s, s == s, s != s);
}

The error I've got is

<source>(7): error C3615: constexpr function 'operator ==' cannot result in a constant expression

<source>(9): note: failure was caused by call of undefined function or one not declared 'constexpr'

<source>(9): note: see usage of 'std::operator =='

<source>(8): note: while compiling class template member function 'bool operator ==(const A<std::string> &,const A<std::string> &)'

<source>(29): note: see reference to class template instantiation 'A<std::string>' being compiled

Godbolt link

Is this 'friend' discrimination is a requirement of the standard or a compiler bug?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Nikki Chumakov
  • 1,215
  • 8
  • 18
  • No error with gcc or clang, probably an msvc bug. – n. m. could be an AI Feb 23 '19 at 16:06
  • It should not compile with GCC or Clang, unless they have extended the standard with `constexpr` overloads of `std::string`'s comparison operators. In the [C++ reference](https://de.cppreference.com/w/cpp/string/basic_string/operator_cmp) there are no `constexpr` overloads. – j00hi Feb 24 '19 at 02:11
  • @j00hi why does msvc only complain about the friend operator? Make it a non-friend and it's compiles. Compiler conspiracy? – n. m. could be an AI Feb 24 '19 at 04:55
  • MSVC seems to be more picky about friend operator declarations, at least in templated types. See my [second answer](https://stackoverflow.com/a/54866446/387023) for the fix. – j00hi Feb 25 '19 at 12:43
  • @j00hi A constexpr function template instantiation that fails to satisfy the constexpr requirements is **not** an error. – n. m. could be an AI Feb 25 '19 at 20:35

2 Answers2

0

I think, the error message which you got can be quite misleading or at least confusing. The problem with your code is an incorrect friend-declaration.

Declaring an operator inside a templated struct as being a friend makes it a free function, just like operator< in your example, hence the two parameters instead of only one parameter like it is the case for operator!= as you have declared it in your example. If you would declare operator< as a friend of struct A, the correct way to do so would be:

template <typename  X>
friend constexpr bool operator< (const A<X>& l, const A<X>& r);

The same goes for operator==. The proper declaration would be:

template <typename X>
friend constexpr bool operator== (const A<X>& l, const A<X>& r)
{
    return l.Value == r.Value;
}

i.e. WITH template <typename X> which you left out in your problematic example and therefore, didn't compile. Your original operator== declaration would not result in a proper friend-free-function-operator for struct A.

The complete code listing including the fix would be the following:

template <typename T>
struct A
{
    T Value;

    // no error anymore
    template <typename X>
    friend constexpr bool operator== (const A<X>& l, const A<X>& r)
    {
        return l.Value == r.Value;
    }

    // ok
    constexpr bool operator!= (const A& r) const
    {
        return Value != r.Value;
    }
};

You could also declare it like follows

template <typename T>
friend constexpr bool operator== (const A<T>& l, const A<T>& r)

with T instead of X but it is actually the same, since the inner T overrides the outer T.

j00hi
  • 5,420
  • 3
  • 45
  • 82
0

[dcl.constexpr] says this:

If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression.

So it's perfectly OK to instantiate your class template with std::string, and your comparison functions are still constexpr, though calls to them are not constant expressions (check this by declaring ret constexpr). Marking these functions constexpr won't buy you anything at all (in this instantiation), but it's perfectly legal.

The standard then goes on to say

If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed, no diagnostic required.

This doesn't seem to be applicable to your case however, as instantiations with e.g. built-in types do satisfy constexpr requirements.

The compilation error of MSVC doesnt't seem justified. It looks like a bug in the compiler.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243