source code
https://github.com/kennywakeland/Decltype-Bug/blob/master/main.cpp
Test compile
https://coliru.stacked-crooked.com/a/443f03625728f00e
This code is to test the setters and getters, using macros. I want to pass in the variable name. decltype is not returning the right type to the HTestSet. Any idea why it is not returning the correct value type ?
Compiler error
g++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:26:72: error: no matching function for call to 'HTestSet(TestClass&, void (BaseClass::*)(int), int&)'
if(HTestSet(OBGEC_ON, &decltype(OBGEC_ON)::SET_CALL, test_vall_stor)){
^
main.cpp:18:1: note: in expansion of macro 'H_TEST_VALUE_GET_SET'
H_TEST_VALUE_GET_SET(H_GET_VALUE(OBGEC_ON, VAL_NAME), \
^~~~~~~~~~~~~~~~~~~~
main.cpp:92:5: note: in expansion of macro 'H_TEST_VALUE'
H_TEST_VALUE(woo, Green, 3);
^~~~~~~~~~~~
This is not a bug (as far I can tell) and has nothing to do with decltype. The problem is that setGreen is a non-virtual member function of BaseClass, and since you did not re-declare such function in TestClass, the type of:
&TestClass::setGreen
is:
void (BaseClass::*)(int)
...and thus you have a template parameter conflict in your template instantiation of HTestSet.
I did not find a better standard quote for this than the example in [expr.unary.op#3]:
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static or variant member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m. [...]
struct A { int i; };
struct B : A { };
... &B::i ... // has type int A::*
The use of f at the beginning of your main only works because a void (BaseClass::*)(int) can be implicitly converted to a void (TestClass::*)(int) in this case, [conv.mem#2]:
A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a derived class ([class.derived]) of B. [...] Since the result has type “pointer to member of D of type cv T”, indirection through it with a D object is valid. The result is the same as if indirecting through the pointer to member of B with the B subobject of D.
Related
Is there a technical reason why std::exchange does not work on std::vector::reference or is it a bug in the implementation of GCC and Clang? With MSVC it compiles fine.
I have a setup like this (minimal example)
struct Manager
{
std::vector<bool> lifeTimes;
//Should return the state before trying to kill it
bool kill(std::size_t index)
{
return std::exchange(lifeTimes[index], false);
}
};
std::exchange would make this a really nice one liner but GCC complains about:
error: cannot bind non-const lvalue reference of type ‘std::_Bit_reference&’ to an rvalue of type ‘std::vector::reference’ {aka ‘std::_Bit_reference’}
So it seams it complains about the false since only the second parameter is an rvalue
It is not a bug, MSVC compiles your code because it has an extension which enables binding temporary object (Rvalue) to non-const Lvalue reference.
Below code compiles with MSVC:
void foo(int& i) {}
foo(20); // you are passing Rvalue and it is bound to Lvalue reference
Above code doesn't compile under G++ or CLang, when you add const to make reference to
const Lvalue, it works:
void foo(const int&){}
foo(20); // you can bind Rvalue to const Lvalue reference
A few words about vector. operator[] for vector<T> where T is every type except bool returns T&:
T& vector<T>::operator[](index) // where T is not bool
For bool vector class template has specialization. Values of bool are stored to hold one bit space, because you cannot use address-of operator for one bit, vector<bool>::operator[](index) cannot return reference. vector<bool> has inner proxy class which manipulates bits (call this class as reference).
vector<bool>::reference vector<bool>::operator[](index)
^^^^^^^^^
as you see object of proxy is passed by value.
So when you call
return std::exchange(lifeTimes[index], false);
you are passing temporary objecy (Rvalue) to exchange which takes first argument by reference to non-const Lvalue. This is the cause that G++ discards this code. If you want to compile it you can explicitly create Lvalue object of proxy class and pass it:
bool kill(std::size_t index)
{
std::vector<bool>::reference proxyForBit = lifeTimes[index];
return std::exchange(proxyForBit, false);
}
Background
Everybody agrees that
using <typedef-name> = <type>;
is equivalent to
typedef <type> <typedef-name>;
and that the former is to be preferred to the latter for various reasons (see Scott Meyers, Effective Modern C++ and various related questions on stackoverflow).
This is backed by [dcl.typedef]:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword
becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that
typedef-name. Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.
However, consider a declaration such as
typedef struct {
int val;
} A;
For this case, [dcl.typedef] specifies:
If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the
declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage
purposes only (3.5).
The referenced section 3.5 [basic.link] says
A name having namespace scope that has not
been given internal linkage above has the same linkage as the enclosing namespace if it is the name of
[...]
an unnamed class defined in a typedef declaration in which the class has
the typedef name for linkage purposes [...]
Assuming the typedef declaration above is done in the global namespace, the struct A would have external linkage, since the global namespace has external linkage.
Question
The question is now whether the same is true, if the typedef declaration is replaced by an alias declaration according to the common notion that they are equivalent:
using A = struct {
int val;
};
In particular, does the type A declared via the alias declaration ("using") have the same linkage as the one declared via the typedef declaration?
Note that [decl.typedef] does not say that an alias declaration is a typedef declaration (it only says that both introduce a typedef-name) and that [decl.typedef] speaks only of a typedef declaration (not an alias declaration) having the property of introducing a typedef name for linkage purposes.
If the alias declaration is not capable of introducing a typedef name for linkage purposes, A would just be an alias for an anonymous type and have no linkage at all.
IMO, that's at least one possible, albeit strict, interpretation of the standard. Of course, I may be overlooking something.
This raises the subsequent questions:
If there is indeed this subtle difference, is it by intention or is
it an oversight in the standard?
What is the expected behavior of compilers/linkers?
Research
The following minimal program consisting of three files (we need at least two separate compilation units) is used to investigate the issue.
a.hpp
#ifndef A_HPP
#define A_HPP
#include <iosfwd>
#if USING_VS_TYPEDEF
using A = struct {
int val;
};
#else
typedef struct {
int val;
} A;
#endif
void print(std::ostream& os, A const& a);
#endif // A_HPP
a.cpp
#include "a.hpp"
#include <iostream>
void print(std::ostream& os, A const& a)
{
os << a.val << "\n";
}
main.cpp
#include "a.hpp"
#include <iostream>
int main()
{
A a;
a.val = 42;
print(std::cout, a);
}
GCC
Compiling this with gcc 7.2 with the "typedef" variant compiles cleanly and provides the expected output:
> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42
The compilation with the "using" variant produces a compile error:
> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
a.cpp:4:6: warning: ‘void print(std::ostream&, const A&)’ defined but not used [-Wunused-function]
void print(std::ostream& os, A const& a)
^~~~~
In file included from main.cpp:1:0:
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’, declared using unnamed type, is used but never defined [-fpermissive]
void print(std::ostream& os, A const& a);
^~~~~
a.hpp:9:2: note: ‘using A = struct<unnamed>’ does not refer to the unqualified type, so it is not used for linkage
};
^
a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’ used but never defined
void print(std::ostream& os, A const& a);
^~~~~
This looks like GCC follows the strict interpretation of the standard above and makes a difference concerning linkage between the typedef and the alias declaration.
Clang
Using clang 6, both variants compile and run cleanly without any warnings:
> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp
> ./a.out
42
> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp
> ./a.out
42
One could therefore also ask
Which compiler is correct?
This looks to me like a bug in GCC.
Note that [decl.typedef] does not say that an alias declaration is a typedef declaration
You're right, [dcl.dcl]p9 gives a definition of the term typedef declaration which excludes alias-declarations. However, [dcl.typedef] does explicitly say, as you quoted in your question:
2 A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. [...]
"The same semantics" doesn't leave any doubt. Under GCC's interpretation, typedef and using have different semantics, therefore the only reasonable conclusion is that GCC's interpretation is wrong. Any rules applying to typedef declarations must be interpreted as applying to alias-declarations as well.
It looks like the standard is unclear on this.
On one hand,
[dcl.typedef] A typedef-name can also be introduced by an alias-declaration. [...] Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.
On the other hand, the standard clearly separates the notions of typedef declaration and alias-declaration (the latter term is a grammar production name, so it is italicised and hyphenated; the former is not). In some contexts it talks about "a typedef declaration or alias-declaration", making them equivalent in these contexts; and sometimes it talks solely about "a typedef declaration". In particular, whenever the standard talks about linkage and typedef declarations, it only talks about typedef declarations and does not mention alias-declaration. This includes the key passage
[dcl.typedef] If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage
purposes only.
Note the standard insists on the first typedef-name being used for linkage. This means that in
typedef struct { int x; } A, B;
only A is used for linkage, and B is not. Nothing in the standard indicates that a name introduced by alias-declaration should behave like A and not like B.
It is my opinion that the standard is insufficiently clear in this area. If the intent is to make only typedef declaration work for linkage, then it would be appropriate to state explicitly in [dcl.typedef] that alias-declaration does not. If the intent is to make alias-declaration work for linkage, this should be stated explicitly too, as is done in other contexts.
On compiling the following C program, GCC emits a warning message which is somewhat confusing.
Program Source
#include <stdio.h>
typedef struct {
int x;
} dummy_t;
void myfunc (dummy_t *pointer)
{
printf("x = %d\n", pointer->x);
}
int main ()
{
dummy_t d = { 10 };
/* INCORRECT. */
myfunc((struct dummy_t *)&d);
/* Correct. */
// myfunc((dummy_t *)&d);
return 0;
}
Compilation
bash$ gcc c.c
c.c: In function ‘main’:
c.c:17:20: warning: passing argument 1 of ‘myfunc’ from incompatible pointer type
myfunc((struct dummy_t *)&d);
^
c.c:7:6: note: expected ‘struct dummy_t *’ but argument is of type ‘struct dummy_t *’
void myfunc (dummy_t *pointer)
Notice how both the expected type and the argument type are reported to have the same value struct dummy_t *. This is confusing.
Shouldn't the expected type be dummy_t *?
The above program is a simplified version of the actual code where I faced this problem.
GCC Version
bash$ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
You're right that the error message is confusing. A newer version gives a much better error message:
note: expected 'dummy_t * {aka struct <anonymous> *}' but argument is
of type 'struct dummy_t *'
As you can see, dummy_t and struct dummy_t are different types. With this declaration:
typedef struct {
int x;
} dummy_t;
You are typedef'ing an anonymous struct. However, later when you do struct dummy_t, you are forward declaring a new struct named dummy_t. Clearly, these are two different types, hence the error.
The code below compiles in Visual Studio 2013, gcc 4.8, clang 3.4 and clang 3.5 (Apple LLVM 6.0) but does not compile in clang 3.6 (via Apple LLVM 6.1)
The code is a simplified version of a complicated class in our codebase which is the minimum required to exhibit the issue.
The crux of the problem is that the copy construction of TYPED_VALUE is, in 3.6, evaluating the templated conversion operator for type STRING because of the presence of a constructor that accepts a STRING; this causes std::is_constructible to be evaluated which leads to it needing the definition of STRING (which we cannot provide here - would lead to a circular dependency in the full code).
class STRING;
class TYPED_VALUE
{
public:
TYPED_VALUE( const TYPED_VALUE& ) = default; // explicit or implicit doesn't make a difference
TYPED_VALUE( const STRING & ) {}
template< typename TYPE, typename std::enable_if<!std::is_pointer< TYPE >::value && !std::is_constructible< TYPE, const STRING& >::value && !std::is_constructible< TYPE, bool >::value, int >::type = 0 >
operator TYPE( void ) const = delete;
};
class TYPED_STORAGE
{
public:
TYPED_STORAGE( const TYPED_VALUE &v ) : value( v ) {}
TYPED_VALUE value;
};
The error message is
/type_traits:2329:38: error: incomplete type 'SICORE::STRING' used in type trait expression
: public integral_constant<bool, __is_constructible(_Tp, _Args...)>
^
/main.cpp:348:99: note: in instantiation of template class 'std::__1::is_constructible<SICORE::STRING, const SICORE::STRING &>' requested here
template< typename TYPE, typename std::enable_if<!std::is_pointer< TYPE >::value && !std::is_constructible< TYPE, const STRING& >::value && !std::is_constructible< TYPE, bool >::value, int >::type = 0 >
^
/main.cpp:349:9: note: while substituting prior template arguments into non-type template parameter [with TYPE = SICORE::STRING]
operator TYPE( void ) const = delete;
^~~~~~~~~~~~~~~~~~~~~~~~~~~
/main.cpp:355:56: note: while substituting deduced template arguments into function template 'operator type-parameter-0-0' [with TYPE = SICORE::STRING, $1 = (no value)]
TYPED_STORAGE( const TYPED_VALUE &v ) : value( v ) {}
^
/main.cpp:340:11: note: forward declaration of 'SICORE::STRING'
class STRING;
^
To me this seems like a bug in 3.6, in previous versions the overload resolution determines that the copy constructor is the best fit without having to evaluate the template arguments - I tried to understand the overload resolution notes in the standard but I think that just confused me more ;)
(This can be fixed by making either the constructor or the conversion operator explicit I realise, but that is not the behaviour we want)
Any standard experts out there know the answer?
I believe Clang is correct to produce this error:
The [temp.inst] section of the C++ standard in paragraph 10 says:
If a function template or a member function template specialization is
used in a way that involves overload resolution, a declaration of the
specialization is implicitly instantiated (14.8.3).
Forming the implicit conversion sequence necessary to rank the overload candidates for the call to TYPE_VALUE's constructor requires the instantiation of the conversion operator. And the use of an incomplete type parameter to the trait doesn't form an invalid type, so this isn't a substitution failure, it is a hard error.
The copy constructor of TYPED_VALUE uses a reference to STRING, it should not be evaluated.
I think this is a clang error.
I haven't read the new c++ standard for a long time, however, I couldn't make sure it hadn't changed.
templates are instantiated as-needed, and I think Clang 3.6 implemented a DR where it needed to instantiate a template earlier than 3.5 did.
Clang's own diagnostics propaganda contains this exerpt:
Since Clang has range highlighting, it never needs to pretty print your code back out to you. This is particularly bad in G++ (which often emits errors containing lowered vtable references), but even GCC can produce inscrutible error messages in some cases when it tries to do this.
Googling this phrase doesn't give anything very helpful, and the subsequent example is completely unrelated.
Can someone please post an example of what it's talking about?
Thanks.
Here is an example:
struct a {
virtual int bar();
};
struct foo : public virtual a {
};
void test(foo *P) {
return P->bar()+*P;
}
Clang produces:
t.cc:9:18: error: invalid operands to binary expression ('int' and 'foo')
return P->bar()+*P;
~~~~~~~~^~~
GCC 4.2 produces:
t.cc: In function ‘void test(foo*)’:
t.cc:9: error: no match for ‘operator+’ in ‘(((a*)P) + (*(long int*)(P->foo::<anonymous>.a::_vptr$a + -0x00000000000000020)))->a::bar() + * P’
t.cc:9: error: return-statement with a value, in function returning 'void'
GCC does this because its C++ frontend is bolted on top of the C frontend in many cases. Instead of building C++-specific Abstract Syntax Trees (ASTs) for various C++ operations, the parser just lowers them immediately to their C equivalent. In this case, GCC synthesizes an struct to contain the vtable, and the pointer dereference to bar is then lowered into a series of C pointer dereferences, casts, pointer arithmetic etc.
Clang does not have this problem, because it has a very clean AST that directly represents the source code. If you change the example to:
struct a {
virtual int bar();
};
struct foo : public virtual a {
};
void test(foo *P) {
P->bar();
}
.. so that the code is valid, then ask clang to dump its ast with "clang -cc1 -ast-dump t.cc", you get:
...
void test(foo *P)
(CompoundStmt 0x10683cae8 <t.cc:8:19, line:10:1>
(CXXMemberCallExpr 0x10683ca78 <line:9:3, col:10> 'int'
(MemberExpr 0x10683ca40 <col:3, col:6> '<bound member function type>' ->bar 0x10683bef0
(ImplicitCastExpr 0x10683cac8 <col:3> 'struct a *' <UncheckedDerivedToBase (virtual a)>
(ImplicitCastExpr 0x10683ca28 <col:3> 'struct foo *' <LValueToRValue>
(DeclRefExpr 0x10683ca00 <col:3> 'struct foo *' lvalue ParmVar 0x10683c8a0 'P' 'struct foo *'))))))
-Chris