I'm trying to explicitly instantiate a forwarding constructor using C++20 with GCC 13.3 compiler in order not to put its definition in the header file. Here is a minimal example:
Foo.h
#pragma once
#include <concepts>
#include <string>
class Foo {
public:
template <class String>
requires std::convertible_to<String, std::string>
explicit Foo(String&& s);
private:
std::string m_s;
};
Foo.cpp
#include "Foo.h"
template <class String>
requires std::convertible_to<String, std::string>
Foo::Foo(String&& s) : m_s{std::forward<String>(s)} {}
template Foo::Foo(const std::string& s);
template Foo::Foo(std::string&& s);
test.cpp
#include "Foo.h"
int main() {
Foo f1{"test"};
std::string test = "test";
Foo f2{test};
return 0;
}
The code compiles but fails to link with the following error:
[main] Building folder: /home/bobeff/projects/cpp/test/build/Debug
[build] Starting build
[proc] Executing command: /usr/bin/cmake --build /home/bobeff/projects/cpp/test/build/Debug --config Debug --target all --
[build] [2/3 33% :: 0.149] Building CXX object CMakeFiles/test.dir/test.cpp.o
[build] [2/3 66% :: 0.156] Building CXX object CMakeFiles/test.dir/Foo.cpp.o
[build] [3/3 100% :: 0.188] Linking CXX executable test
[build] FAILED: test
[build] : && /usr/bin/g++ -g CMakeFiles/test.dir/test.cpp.o CMakeFiles/test.dir/Foo.cpp.o -o test && :
[build] /usr/bin/ld: CMakeFiles/test.dir/test.cpp.o: in function `main':
[build] /home/bobeff/projects/cpp/test/test.cpp:4:(.text+0x31): undefined reference to `Foo::Foo<char const (&) [5]>(char const (&) [5])'
[build] /usr/bin/ld: /home/bobeff/projects/cpp/test/test.cpp:6:(.text+0x72): undefined reference to `Foo::Foo<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)'
[build] collect2: error: ld returned 1 exit status
[build] ninja: build stopped: subcommand failed.
[proc] The command: /usr/bin/cmake --build /home/bobeff/projects/cpp/test/build/Debug --config Debug --target all -- exited with code: 1
[driver] Build completed: 00:00:00.220
[build] Build finished with exit code 1
How to do this correctly?
Your explicit instantiations work, but you use another ones (char const (&) [5]
, and std::string&
respectively).
How to do this correctly?
Without exposing the template definition, as you only support std::string&&
and const std::string&
, provide only that to you public interface:
class Foo {
public:
explicit Foo(std::string&&);
explicit Foo(const std::string&);
#if 1 // Assuming still useful to factorize more complex code
private:
struct Tag{};
template <class String>
requires std::convertible_to<String, std::string>
explicit Foo(Tag, String&& s);
#endif
private:
std::string m_s;
};
and forward/delegate to your template one (if the factorisation is still required):
#include "Foo.h"
template <class String>
requires std::convertible_to<String, std::string>
Foo::Foo(Tag, String&& s) : m_s{std::forward<String>(s)} {}
Foo::Foo(const std::string& s) : Foo(Tag{}, s) {}
Foo::Foo(std::string&& s) : Foo(Tag{}, std::move(s)) {}
// No more explicit instantiation, implicit one is sufficient