So, this is eating me for the last two days:
I can't link my application, depending on which compiler I use and what optimizations levels are used:
gcc plays nicely with -O1, -O2, -O3 but fails with -O0 and
clang plays nicely with -O2 and -O3 and fails with -O1 and -O0.
There are a bunch of horrible templates in it, but I see no reason for this obscure behaviour.
Here is a minimal amount of code that reproduces the problem:
#include <map>
#include <memory>
#include <iostream>
using id_type = long;
class CB : public std::enable_shared_from_this<CB>
{
public:
CB() = default;
virtual ~CB() = default;
};
template<class F, class C> class SC : public CB
{
};
class FB
{
public:
virtual ~FB() = default;
template<class T, class B> T* as(B* v) const { return dynamic_cast<T*>(v);}
};
template<class T>
class F : public FB
{
public:
virtual std::shared_ptr<CB> create() const
{
auto n = std::make_shared<T>();
return n;
}
};
struct B
{
virtual ~B() = default;
static const id_type ID = 1;
};
class A : virtual public B, virtual public SC<A, B>
{
public:
A() = default;
};
static std::map<id_type, std::shared_ptr<FB>> crtrs {
{A::ID, std::make_shared<F<A>>()}
};
int main()
{
std::cout << crtrs.size();
}
Here is the same online https://gcc.godbolt.org/z/sb9b5E
And here are the error messages:
fld#flap ~/work/p/test1
> $ g++ -O1 main.cpp
fld#flap ~/work/p/test1
> $ g++ -O2 main.cpp
fld#flap ~/work/p/test1
> $ g++ -O3 main.cpp
fld#flap ~/work/p/test1
> $ g++ -O4 main.cpp
fld#flap ~/work/p/test1
> $ g++ -O0 main.cpp
/tmp/cc8D7sNK.o: In function `__static_initialization_and_destruction_0(int, int)':
main.cpp:(.text+0x1c0): undefined reference to `B::ID'
collect2: error: ld returned 1 exit status
fld#flap ~/work/p/test1
> $ clang++ -O0 main.cpp
/tmp/main-c49b32.o: In function `__cxx_global_var_init.1':
main.cpp:(.text.startup+0x7a): undefined reference to `B::ID'
clang-8: error: linker command failed with exit code 1 (use -v to see invocation)
fld#flap ~/work/p/test1
> $ clang++ -O1 main.cpp
/tmp/main-cf18ee.o: In function `__cxx_global_var_init.1':
main.cpp:(.text.startup+0x3c): undefined reference to `B::ID'
clang-8: error: linker command failed with exit code 1 (use -v to see invocation)
fld#flap ~/work/p/test1
> $ clang++ -O2 main.cpp
fld#flap ~/work/p/test1
> $ clang++ -O3 main.cpp
If someone has any ideas what might be the reason, any hints are more than welcome.
You did not provide a definition of B::ID anywhere. It just happens that with higher optimization all accesses happened to be elided by the compiler.
You need to add the definition of a static member at toplevel scope:
const id_type B::ID;
If a static const data member is only read, it does not need a separate definition because compile time constants are not considered ODR-used (One Definition Rule).
The reason that you need the definition at all is that the map constructor expects std::map::value_type in the initializer list which is std::pair<const Key, T>. The constructor that gets picked in this case is pair( const T1& x, const T2& y );
In order to call this constructor the address of A::ID which is B::ID gets taken, which constitutes an ODR-use even for a constant.
Because the pair constructor is almost trivial in this case it gets inlined at higher optimization and the only reference to &B::ID disappears because B::ID's value is known and the pair can be directly initialized.
See also: static
If you are using C++17 or newer, you can also make B:ID constexpr instead of const, then you do not need a separate definition, because constexpr is implicitly inline (inline static const should also be OK).
Related
Sample code:
#include <string>
#include <set>
using namespace std;
class x
{
private:
int i;
public:
int get_i() const { return i; }
};
struct x_cmp
{
bool operator()(x const & m1, x const & m2)
#if _MSC_VER
const
#endif
{
return m1.get_i() > m2.get_i();
}
};
std::set<x, x_cmp> members;
void add_member(x const & member)
{
members.insert(member);
}
Invocations:
$ g++ -c -std=c++14 -pedantic -Wall -Wextra
<nothing>
$ clang++ -c -std=c++14 -pedantic -Wall -Wextra
<nothing>
$ icc -c -std=c++14 -pedantic -Wall -Wextra
<nothing>
$ cl /c /std:c++14 /Za
<nothing>
Question: why msvc requires const while others don't? Or why others don't require const?
This is LWG2542. In C++14, the wording for the comparison operator said "possibly const", which was interpreted by GCC and Clang as meaning that the comparison operator was not required to be const qualified. MSVC always required it.
This is a wording defect, as comparison operators for associative containers should be const qualified. This was changed in C++17 to require the comparator to be const. This is a breaking change, so valid (though broken) C++14 code may fail to compile in C++17.
Given this code:
template <typename>
struct Check { constexpr static bool value = true; };
struct A {
A() noexcept(Check<int>::value) = default;
};
Compiling with various versions of GNU g++ works fine, but it always fails with clang++ 5.0.1 (both with libstdc++ and libc++):
$ g++-4.9.4 test.cpp -c -o test -std=c++11 -Wall -Wextra
$ g++-5.4.0 test.cpp -c -o test -std=c++11 -Wall -Wextra
$ g++-6.4.0 test.cpp -c -o test -std=c++11 -Wall -Wextra
$ g++-7.2.0 test.cpp -c -o test -std=c++11 -Wall -Wextra
$ clang++ test.cpp -c -o test -std=c++11 -Wall -Wextra -Weverything
test.cpp:2:16: warning: 'constexpr' specifier is incompatible with C++98 [-Wc++98-compat]
struct Check { constexpr static bool value = true; };
^
test.cpp:5:39: warning: defaulted function definitions are incompatible with C++98 [-Wc++98-compat]
A() noexcept(Check<int>::value) = default;
^
test.cpp:5:9: warning: noexcept specifications are incompatible with C++98 [-Wc++98-compat]
A() noexcept(Check<int>::value) = default;
^
test.cpp:5:5: error: exception specification is not available until end of class definition
A() noexcept(Check<int>::value) = default;
^
test.cpp:5:18: note: in instantiation of template class 'Check<int>' requested here
A() noexcept(Check<int>::value) = default;
^
3 warnings and 1 error generated.
$ clang++ test.cpp -c -o test -std=c++11 -Wall -Wextra
test.cpp:5:5: error: exception specification is not available until end of class definition
A() noexcept(Check<int>::value) = default;
^
test.cpp:5:18: note: in instantiation of template class 'Check<int>' requested here
A() noexcept(Check<int>::value) = default;
^
1 error generated.
On Compiler Explorer this also seems to work for all versions of GCC newer 4.7.0 (including trunk), but fails for all Clang versions except for Clang 3.4.0, 3.5.0, 3.5.1 and trunk. So this seems like Clang bug.
Is it possible to work around this bug? How? Is this bug already tracked somewhere?
EDIT:
I tracked this bug down to Clang PR23383. As of now there is no notice about this being fixed in Clang although it seems to work with Clang trunk in Compiler Explorer.
This might be related to PR30860 (and C++ DR1330 as described in Richard Smith's comment on PR30860). It seems that the issue has something to do about when the contents of the noexcept() are parsed.
And so it turns out that one obvious workaround is to try to force the compiler to parse these contents elsewhere. One possible solution would be to provide the contents noexcept() as a constexpr member constant:
template <typename>
struct Check { constexpr static bool value = true; };
struct A {
A() noexcept(workaround) = default;
private: /* Work around Clang PR23383: */
static constexpr bool workaround = Check<int>::value;
};
Unfortunately, this does not work in all cases, e.g. such as this:
#include <type_traits>
struct Base { virtual ~Base() noexcept; };
struct OuterClass {
class InnerDerived: public Base {
private:
static constexpr auto const workaround =
std::is_nothrow_default_constructible<Base>::value;
public:
InnerDerived() noexcept(workaround);
};
class InnerDerived2: public InnerDerived {
private:
static constexpr auto const workaround2 =
std::is_nothrow_default_constructible<InnerDerived>::value;
public:
InnerDerived2() noexcept(workaround2);
};
};
The latter yields the error even when using Clang trunk in Compiler Explorer. Any ideas why?
Given the following code as test.cpp,
Building using clang++ -c test.cpp -o test.o -g; clang++ test.o -g -all_load, setting breakpoint at return a.getValue(); and attempting to p a.getValue() from lldb:
Running llvm 3.8.0 on unix - works perfectly
Running xcode or llvm 8.1.0 on OSX - I get the following error:
error: Couldn't lookup symbols:
__ZNK4Test7MyClassILi2ELi3EE8getValueEv
Two interesting facts:
If I remove the last template argument - all works well
If I build directly without going through the .o file (clang++ test.cpp) = all goes well
Anyone has a clue what is going on, and how can it be fixed?
namespace Test{
template<class T>
class BLA{
public:
T getBlaValue() const{return 3;}
};
template <int N1, int N2, template<class T>class Impl = BLA>
class MyClass {
private:
public:
__attribute__((used))
int getValue() const
{
return 3;
}
};
}
int main()
{
Test::MyClass<2, 3> a;
return a.getValue();
}
Using Clang++ (v3.8.0), the following code fails to link due to sSomeValue being an undefined reference.
#include <iostream>
struct MyClass
{
static constexpr int sSomeSize = 3;
static constexpr int sSomeValue = 10;
};
int foo()
{
int someArray[MyClass::sSomeSize] = {};
std::fill(std::begin(someArray), std::end(someArray), MyClass::sSomeValue);
return someArray[0];
}
int main()
{
std::cout << foo() << std::endl;
}
More precisely:
clang++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
/tmp/main-c8de0c.o: In function `foo()':
main.cpp:(.text+0x2): undefined reference to `MyClass::sSomeValue'
/tmp/main-c8de0c.o: In function `main':
main.cpp:(.text+0x16): undefined reference to `MyClass::sSomeValue'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
However, changing the definition of foo to
int foo()
{
int someArray[MyClass::sSomeSize] = {MyClass::sSomeValue, MyClass::sSomeValue, MyClass::sSomeValue};
return someArray[0];
}
does not exhibit the same linker error, even though sSomeValue is still being used.
What is going on here? Is the compiler doing some optimization around the std::fill call that I may not be aware of?
Note that g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out compiles, links, and outputs 10 as expected with v6.3.0.
The error is nothing to do with std::fill.
If you refer below documentation, it says: Reference variables can be declared constexpr (their initializers have to be reference constant expressions):
http://en.cppreference.com/w/cpp/language/constexpr
Slight modification of your code works fine. Just make the struct variables "const &" as said above.
#include <iostream>
struct MyClass
{
static constexpr int const& sSomeSize = 3;
static constexpr int const& sSomeValue = 10;
};
int foo()
{
int someArray[MyClass::sSomeSize] = {};
std::fill(std::begin(someArray), std::end(someArray), MyClass::sSomeValue);
return someArray[0];
}
int main()
{
std::cout << foo() << std::endl;
}
Also refer here for well explained article about constexpr and static
"Does static constexpr variable make sense?
Specially the last para in ticked answer.
I have noticed a strange behavior when trying to compile the code included below. I have 4 files as follows
createshared.h:
#ifndef CREATESHARED_H_
#define CREATESHARED_H_
#include <memory>
#include <utility>
#ifdef USE_REFREF
template<typename T, typename... Args>
std::shared_ptr<T> create_shared(Args&&... args)
{
class HelperClass : public T
{
public:
HelperClass (Args&& ... nargs) : T(std::forward<Args...>(nargs)...) {}
virtual ~HelperClass() = default;
};
return std::make_shared<HelperClass>(std::forward<Args...>(args)...);
}
#else
template<typename T, typename... Args>
std::shared_ptr<T> create_shared(Args... args)
{
class HelperClass : public T
{
public:
HelperClass (Args ... nargs) : T(nargs...) {}
virtual ~HelperClass() = default;
};
return std::make_shared<HelperClass>(args...);
}
#endif
#endif
staticinitclass.h
#ifndef STATICINITCLASS_H_
#define STATICINITCLASS_H_
class StaticInitClass
{
public:
#ifdef INITIALIZE_IN_HEADER
static const int default_i = 1;
#else
static const int default_i;
#endif
virtual ~StaticInitClass() = default;
StaticInitClass() = delete;
protected:
StaticInitClass(int i);
};
#endif
staticinitclass.cpp:
#include "staticinitclass.h"
#include <iostream>
#ifndef INITIALIZE_IN_HEADER
const int StaticInitClass::default_i = 2;
#endif
StaticInitClass::StaticInitClass(int i)
{
std::cout << "Created with " << i << std::endl;
}
main.cpp:
#include "staticinitclass.h"
#include "createshared.h"
#include <memory>
int main(int argc, const char* argv[])
{
auto shared = create_shared<StaticInitClass>(StaticInitClass::default_i);
}
With no flags, the program compiles and runs fine.
$ g++ -std=c++11 main.cpp staticinitclass.cpp
$ ./a.out
Created with 2
Fine, because default_i is an integral type, we can initialize it in the header. Let's do that
$ g++ -std=c++11 main.cpp staticinitclass.cpp -DINITIALIZE_IN_HEADER
$ ./a.out
Created with 1
Good, still compiles and works fine. Now, let's add our && and std::forward
$ g++ -std=c++11 main.cpp staticinitclass.cpp -DINITIALIZE_IN_HEADER -DUSE_REFREF
/tmp/cc3G4tjc.o: In function `main':
main.cpp:(.text+0xaf): undefined reference to `StaticInitClass::default_i'
collect2: error: ld returned 1 exit status
Linker error. Well, let's now try initializing our default_i member in the .cpp
$ g++ -std=c++11 main.cpp staticinitclass.cpp -DUSE_REFREF
$ ./a.out
Created with 2
And it works again. Using clang yields the same result, which would lead me to believe that this isn't just an isolated compiler error, but perhaps something in the language itself that prevents the static initialization. I just can't seem to connect why adding && would cause the break.
Currently I am using g++ 4.8.2 and clang++ 3.5 on Ubuntu 14.04
Any ideas what is broken here when using -DINITIALIZE_IN_HEADER and -DUSE_REFREF?
Following ยง9.4.2 [class.static.data]:
3 If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment expression is a constant expression (5.19). [...] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.
In other words, giving a const static data member a value directly in a header does not mean you don't need to define that data member. You should have this in staticinitclass.cpp file:
#ifndef INITIALIZE_IN_HEADER
const int StaticInitClass::default_i = 2;
#else
const int StaticInitClass::default_i; // this is what you don't have
#endif
Binding to a reference (to a forwarding reference && in your case deduced as const lvalue reference) counts as odr-use of this data member.
In case you don't use a forwarding reference and you take the argument by-value, then it is not an odr-use of that static data member, therefore no linker error is raised.