9

Assume the following code:

#include <iostream>

template<typename T>
struct Link
{
    Link(T&& val) : val(std::forward<T>(val)) {}

    T val;
};

template<typename T>
std::ostream& operator<<(std::ostream& out, const Link<T>& link)
{
    out << "Link(" << link.val << ")";
    return out;
}

template<typename T>
auto MakeLink(T&& val) -> Link<T>
{
    return {std::forward<T>(val)};
}

namespace Utils {
template<typename Any>
constexpr auto RemoveLinks(const Any& any) -> const Any&
{
    return any;
}

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))
{
    return RemoveLinks(link.val);
}

} /* Utils */

int main()
{
    int k = 10;

    auto link = MakeLink(MakeLink(k));

    std::cout << link << std::endl;
    std::cout << Utils::RemoveLinks(link) << std::endl;
}

For some reason I can't understand, it generates the following compilation errors with g++-4.8:

/home/allan/Codes/expr.cpp: In instantiation of ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const int&]’:
/home/allan/Codes/expr.cpp:88:32:   required from ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = Link<int&>; decltype (Utils::RemoveLinks(link.val)) = const int&]’
/home/allan/Codes/expr.cpp:100:41:   required from here
/home/allan/Codes/expr.cpp:88:32: error: invalid initialization of reference of type ‘const Link<int&>&’ from expression of type ‘const int’
     return RemoveLinks(link.val);
                                ^
/home/allan/Codes/expr.cpp:89:1: error: body of constexpr function ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const Link<int&>&]’ not a return-statement
 }
 ^
/home/allan/Codes/expr.cpp: In function ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const int&]’:
/home/allan/Codes/expr.cpp:89:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

while clang 3.3 gives:

test.cc:34:12: error: reference to type 'const Link<int &>' could not bind to an lvalue of type 'const int'
return RemoveLinks(link.val);
       ^~~~~~~~~~~~~~~~~~~~~
test.cc:46:25: note: in instantiation of function template specialization 'Utils::RemoveLinks<Link<int &> >' requested here
std::cout << Utils::RemoveLinks(link) << std::endl;

If, however, the namespace Utils is removed, then it compiles without errors (both gcc and clang), and execution outputs:

Link(Link(10))
10

Why defining those template functions (RemoveLinks) in a namespace causes such errors?

Walter
  • 44,150
  • 20
  • 113
  • 196
A.L.
  • 1,133
  • 1
  • 12
  • 19
  • Without `constexpr` it compiles and runs fine in VS (`constexpr` is not supported yet). Looks like a gcc bug. – catscradle Aug 28 '13 at 18:22
  • Interesting. Note that if you remove the namespace and use a qualified name `-> decltype(::RemoveLinks(link.val))`, the error returns. This surely looks like a bug in either two compilers or the C++ standard... – n. m. could be an AI Aug 28 '13 at 18:25
  • @catscradle: Does VS do two-phase name lookup yet? :P – n. m. could be an AI Aug 28 '13 at 18:33
  • @n.m. No, not as far as I know. – catscradle Aug 28 '13 at 18:43
  • @catscradle: I'm asking because the error looks like it could be TPL related. – n. m. could be an AI Aug 28 '13 at 18:52
  • @catscradle If so, both gcc and clang have the same bug. – A.L. Aug 28 '13 at 19:00
  • I think the problem is the point of declaration. If I understand it correctly, the name of the second overload is available only *after* the *trailing-return-type*. [Live example](http://coliru.stacked-crooked.com/view?id=1d129d208a4c7a34a871fe182eb5b172-25dabfc2c190f5ef027f31d968947336) – dyp Aug 29 '13 at 15:55

2 Answers2

7

This problem is a result of an issue with the point of declaration (1) combined with dependent name lookup (2).

(1) In the declaration

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))

the name RemoveLinks, or more precisely, this overload of RemoveLinks, is only visible after the complete declarator according to [basic.scope.pdecl]/1. The trailing-return-type is part of the declarator as per [dcl.decl]/4. Also see this answer.

(2) In the expression RemoveLinks(link.val), the name RemoveLinks is dependent as per [temp.dep]/1, as link.val is dependent.

If we now look up how dependent names are resolved, we find [temp.dep.res]:

In resolving dependent names, names from the following sources are considered:

  • Declarations that are visible at the point of definition of the template.
  • Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.

The first bullet doesn't find the second overload of RemoveLinks because of the point of declaration (1). The second one doesn't find the overload because the namespace Util is not associated with any argument. This is why putting everything in the global namespace or in the namespace Util works as expected (Live example).

For the same reason, using a qualified-id in the trailing-return-type (like -> decltype(Util::RemoveLinks(link.val)) doesn't help here.

Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • Why then, if you put everything in the global namespace *and* say `-> decltype(::RemoveLinks(link.val))`, there's still an error? Isn't `RemoveLinks` still a dependent name? If not, why not? – n. m. could be an AI Aug 30 '13 at 15:50
  • @n.m. Unfortunately, the Standard is a bit vague about the link between *dependent expressions* (as in *type-dependent expression*) and *dependent names*. If we assume a name is dependent if either 1) the *id-expression* "specifying" that name is dependent (like in `T::some_name`, `T` is dependent, therefore this occurrence of `some_name` is dependent) or 2) it fulfils [temp.dep]/1 (unqualified function call), then the expression `::RemoveLinks(link.val)` is dependent, but the name `RemoveLinks` in it is not, because the *id-expression* here is `::RemoveLinks`. – dyp Aug 30 '13 at 20:25
  • @n.m. (continued) This interpretation of the Standard has a weird side effect: e.g. if in the *id-expression* `some_name`, the name `some_name` refers to a nested class, then it is not a dependent name but names a dependent type: It is a dependent type because *name lookup* finds it to be e.g. "a nested class or enumeration that is a member of the current instantiation" [temp.dep.type]/8. That is, (in this reading of the Standard,) types (and expressions) can be dependent even if the names they contain are not. – dyp Aug 30 '13 at 20:25
  • 2
    "I say we take off and nuke the entire site from orbit. It's the only way to be sure." – n. m. could be an AI Aug 30 '13 at 21:19
2

I tried compiling the sample code above with GCC 4.8.1, with clang and also Intel icpc and got the same error messages as you.

I am able to get it to successfully compile without trouble if I revise the signature of the template specialization from:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))

and make the return type as const. This might cause a compiler warning since the const there is meaningless, but that can be ignored. I tested it and it works fine for me with gcc, but not icpc or clang:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val)) const

I found the error message (with the original code) from Intel icpc to be the most informative:

code.cc(48): error: template instantiation resulted in unexpected function type of "auto (const Link<Link<int &>> &)->const int &" (the meaning of a name may have changed since the template declaration -- the type of the template is "auto (const Link<T> &)->decltype((<expression>))") std::cout << Utils::RemoveLinks(link) << std::endl; ^ detected during instantiation of "Utils::RemoveLinks" based on template argument <Link<int &>> at line 48

Unfortunately, the above answer is more of a workaround for gcc rather than an answer to your question. I'll update this if I have anything more / better to add.

EDIT

It appears that decltype(RemoveLinks(link.val)) is actually following the recursion so that it returns int& rather than Link.

EDIT #2

There have been reported bugs in LLVM about crashes caused by decltype recursion problems. It seems that this is definitely a bug of sorts, but one that seems to be present in multiple implementations of C++.

The problem can be fixed quite easily if you create a an alias for type T in the link struct and have decltype refer to the alias rather than to the return type. This will eliminate the recursion. As follows:

template<typename T>
struct Link
{
    Link(T&& val) : val(std::forward<T>(val)) {}
    using value_type = T;
    T val;
};

And then the RemoveLinks signature is changed accordingly to refer to this alias:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(links.value_type)

This code successfully builds on all 3 compilers.

I will file some bug reports with the compilers to see if there's anything they can do about it.

Hope this helps.

Shmuel Levine
  • 550
  • 5
  • 18
  • Did you mean `decltype(Link::value_type)`? (there is no `links`) Also, did you check that the output of the compiled programs was correct? I tried it and got incorrect output. – Walter Aug 29 '13 at 15:45