这是有效的 C++ 11

Is this valid C++ 11

我有以下代码可以在 g++ 下编译,但不能用 clang 编译。

如果以各种小的方式更改代码,例如合并 2 个命名空间声明,Clang 将编译代码。

// The problem disappears without namespaces.
namespace Root {
    // The problem disappears if 'g' is in the global namespace, and we change
    // the friend declaration to '::g'

    // The problem disappears if 'g' has void return type.

    // The problem disappears if we get rid of the 'Value' template argument
    // and the 'value' parameter.
    template<typename Value, typename Defaulted = void>
    bool g(Value value);

    // The problem disappears if MyClass is not a template.
    template<typename ClassValue>
    class MyClass {
    private:
        template<typename Value, typename Defaulted>
        friend bool g(Value value);
    };
}

// The problem disappears if we declare the Root namespace in a single block
// containing 'g', 'MyClass' and 'f'.

// The problem remains if we declare f in the global namespace and reference
// Root::g.
namespace Root {
    void f() {
        MyClass<int> value;

        g(value);
    }
}

用 clang 编译:

clang -fsyntax-only -std=c++11 testcase.cpp

用g++编译:

g++ -fsyntax-only -std=c++11 testcase.cpp

版本是 g++ 4.9.2,clang 3.6.0,都在 Ubuntu 核心 15.04 上。

Clang 给出错误信息:

testcase.cpp:24:9: error: no matching function for call to 'g'
        g(value);
        ^
testcase.cpp:14:21: note: candidate template ignored: couldn't infer template argument 'Defaulted'
        friend bool g(Value value);
                ^
1 error generated.

我认为这是一个 clang 错误。从 [temp.param],我们有:

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

The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are (8.3.6).

后一点意思就是我们可以这样写:

template <typename T, typename U=int>
void h();

template <typename T, typename U>
void h() { }

h<int>();

这是由 clang 编译的完全正确的代码。我们不能根据引用的规则将默认模板参数指定为g,因为g是先前声明的,但不是 指定它仍应保持 Defaulted 可通过合并步骤用作 void。如果默认参数可用,那么 lookup 应该可以找到我们想要的 g

解决方法是简单地加好友我们关心的专业化:

friend bool g<>(MyClass value);