6
votes

Consider this snippet:

#include <utility>

template <typename U>
auto foo() -> decltype(std::declval<U>() + std::declval<U>());

template <typename T>
decltype(foo<T>()) bar(T)
{}

int main()
{
    bar(1);
    return 0;
}

This fires a warning and a static assertion failure in all versions of GCC I tried it on (4.7.3, 4.8.1, 4.9-some-git) when compiled with -Wall -Wextra. For instance, this is the output of 4.8.1:

main.cpp: In instantiation of ‘decltype (foo<T>()) bar(T) [with T = int; decltype (foo<T>()) = int]’:
main.cpp:12:7:   required from here
main.cpp:8:2: warning: no return statement in function returning non-void [-Wreturn-type]
 {}
  ^
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/bits/move.h:57:0,
                 from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/bits/stl_pair.h:59,
                 from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/utility:70,
                 from main.cpp:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/type_traits: In instantiation of ‘typename std::add_rvalue_reference< <template-parameter-1-1> >::type std::declval() [with _Tp = int; typename std::add_rvalue_reference< <template-parameter-1-1> >::type = int&&]’:
main.cpp:8:2:   required from ‘decltype (foo<T>()) bar(T) [with T = int; decltype (foo<T>()) = int]’
main.cpp:12:7:   required from here
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/type_traits:1871:7: error: static assertion failed: declval() must not be used!
       static_assert(__declval_protector::__stop,

If one either disables the warnings or supplies bar with a return statement, e.g.,

template <typename T>
decltype(foo<T>()) bar(T a)
{
    return a + a;
}

the assertion failure disappears. Clang++ 3.3 does not fire assertion errors in any case. Is this standard-conforming behaviour from GCC?

2
Well, not returning from a function with non-void return type is undefined behaviour, so both compilers are right.Kerrek SB
@AndyProwl: You added a statement to bar.aschepler
@Kerrek SB: still, if I wrap the UB so that it does not actually ever gets executed the program should still be well formed? how is this any different from compiling code that dereferences a null pointer? I was under the impression UB should still compile.bluescarni
@KerrekSB: typeid(*a_nullptr) throws an exception, per standard.Xeo

2 Answers

6
votes

My copy of GCC 4.9 built in early June compiles it without complaint as long as I add return {}; or throw; inside the function. With no return, it does fire that static assertion. This is certainly a bug, but only a minor one, since the function only "crashes" upon execution.

I have seen that error when trying to execute declval() in a constant expression, so something like that may be happening in your case. The "cannot be used" is presumably referring to ODR-use or using the result.

Perhaps in the absence of any statement it tried to manufacture one with the content of the decltype. Adding even a simple statement like 0; silences the spurious error. (But static_assert( true, "" ) or even void(0) are insufficient.)

Filed a GCC bug.

2
votes

I don't believe a compiler is within its rights to fail to compile this program; a fatal error for this program is, IME, nonconforming. (The warning, OTOH, is perfectly fine. A compiler can warn about missing return statements, weird inheritance idioms, British/American spelling in identifiers, or racist comments in string literals if it wants, provided it actually compiles valid programs.)

I believe your program produces undefined behavior when run, since you unconditionally execute a function that fails to return a value. However, I don't think that excuses a compiler from actually compiling this program; for all it knows, you compile the program, copy the executable to a tape back-up, and never look at it again. Additionally, I expect that if the compiler emits an error for this program, it would likely emit an error for a program that only called bar() after checking that argc==1, and that certainly would not be within the compiler's UB rights.

Strictly speaking, a compiler doesn't have to be conforming in all circumstances to be considered conforming. A conforming compiler merely has to be able to compile any valid C++ program. Since you can compile this program without -Wall, it can be argued that GCC is conformant without that flag passed. It is surprising that enabling warnings would result in nonconforming errors, though, so I would lean towards characterizing this as a conformance issue in GCC.