Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constexpr is not allowed in declaration of friend template specialization?

I'm porting a C++14-constexpr codebase from Clang to the latest g++-5.1. Consider the following reduced code snippet of a home-grown bitset class that has been compiling correctly since the halcyon days of Clang 3.3 (almost 2 years now!)

#include <cstddef>

template<std::size_t>
class bitset;

template<std::size_t N>
constexpr bool operator==(const bitset<N>& lhs, const bitset<N>& rhs) noexcept;

template<std::size_t N>
class bitset
{
    friend constexpr bool operator== <>(const bitset<N>&, const bitset<N>&) noexcept;
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <-- error from this piece
};

template<std::size_t N>
constexpr bool operator==(const bitset<N>& /* lhs */, const bitset<N>& /* rhs */) noexcept
{
    return true;
}

int main() {}

Live example on Wandbox. However, g++-5.1 and the current trunk release give an error:

'constexpr' is not allowed in declaration of friend template specialization

Question: is this a known g++ bug or is Clang not conforming to the latest Standard?

Note: the above only uses C++11 style constexpr features, since there are no modifications taking place inside operator==, so it seems some weird interference between templates, friends and constexpr.

UPDATE: filed as bug 65977 on Bugzilla.

like image 294
TemplateRex Avatar asked Apr 25 '15 21:04

TemplateRex


1 Answers

GCC is wrong here.

All references are to N4431, the latest C++ WD.

[tl;dr: There's a difference between a function being inline (or more precisely, being an inline function, as defined in 7.1.2/2) and being declared with the inline specifier. The constexpr specifier makes a function inline, but isn't an inline specifier.]

Specifiers are described in subclause 7.1 of the C++ standard, and are an element of the grammar. Therefore, whenever standard talks about a foo specifier appearing somewhere, it means that specifier literally appeared within the (parse tree of the) source code. The inline specifier is a function-specifier, described in subclause 7.1.2, and its effect is to make a function be an inline function. (7.1.2)/2:

A function declaration (8.3.5, 9.3, 11.3) with an inline specifier declares an inline function.

There are two other ways to declare an inline function, without using an inline specifier. One is described in (7.1.2)/3:

A function defined within a class definition is an inline function.

The other is described in (7.1.5)/1:

constexpr functions and constexpr constructors are implicitly inline (7.1.2).

Neither of these says that the behavior is as if an inline specifier were present, merely that the function is an inline function.

So why does this rule exist?

There's a simpler form of this rule in (7.1.2)/3:

If the inline specifier is used in a friend declaration, that declaration shall be a definition or the function shall have previously been declared inline.

The purpose of this is to allow friend declarations to be ignored in most cases -- they are not permitted to add "new information" to the befriended entity, except in the special case where they are defining a friend function. (This in turn allows an implementation to delay parsing a class definition until it's "needed".) Thus we also see, in (8.3.6)/4:

If a friend declaration specifies a default argument expression, that declaration shall be a definition and shall be the only declaration of the function or function template in the translation unit.

And the same applies to a declaration of a friend specialization of a function template: if it could add extra information, then implementations could not delay parsing the class definition.

Now, note that this rationale does not apply to constexpr: if the constexpr specifier appears on any declaration of a function, it must appear on every declaration, per (7.1.5)/1. Since there is no "new information" here, there is no need for a restriction.

like image 179
Richard Smith Avatar answered Oct 23 '22 08:10

Richard Smith