c++templatesconstructorc++20explicit-instantiation

How to explicitly instantiate forwarding constructor in C++?


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:

#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;
};
#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);
#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?


Solution

  • 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