0

I have a problem with linking constexpr constructor between two projects in one Visual Studio solution.

I have two projects in my Visual Studio 2019 Solution:

  • NES_Core
  • NES_Core_Tests

The first one is .lib project, the other is basic GTest project. Both use C++17.

I have the following class declared in executor.h in NES_Core:

namespace nes::cpu::opcodes::immediate {
    class Executor
    {
    public:
        constexpr Executor(registers::Registers& registers) noexcept;
        ~Executor() = default;
        Executor(Executor& rhs) = delete;
        Executor(Executor&& rhs) = delete;
        Executor& operator=(const Executor& rhs) = delete;
        Executor& operator=(Executor&& rhs) = delete;
    private:
        registers::Registers& registers_;
    };
}

And the definition in executor.cpp:

namespace nes::cpu::opcodes::immediate {
    constexpr Executor::Executor(registers::Registers& registers) noexcept :
        registers_(registers)
    {
    }
}

Later on I try to create Executor object in OpcodesImmediateExecutorTests.cpp in NES_Core_Tests project:

#include "pch.h"
#include "nes/cpu/registers/registers.h"
#include "nes/cpu/opcodes/immediate/executor.h"

class OPCodes_ : public ::testing::Test
{
public:
    OPCodes_() :
        reg_(),
        ie_(reg_)
    {

    }
    nes::cpu::registers::Registers reg_;
    nes::cpu::opcodes::immediate::Executor ie_;
};

Unfortunately, it fails on linking:

OpcodesImmediateExecutorTests.obj : error LNK2019: unresolved external symbol "public: __thiscall nes::cpu::opcodes::immediate::Executor::Executor(struct nes::cpu::registers::Registers &)" (??0Executor@immediate@opcodes@cpu@nes@@QAE@AAURegisters@registers@34@@Z) referenced in function "public: __thiscall OPCodes_::OPCodes_(void)" (??0OPCodes_@@QAE@XZ)

What is more, when I remove constexpr keyword from .h and .cpp linking is performed just fine. Do you guys have any ideas why this could happen?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 1
    Why did you make the constructor `constexpr`? Do you understand the ramifications of doing that? – Lightness Races in Orbit Aug 07 '19 at 16:41
  • Why do you think you need so many namespaces? –  Aug 07 '19 at 16:41
  • @LightnessRacesinOrbit , Could you elaborate what you mean by "ramifications"? – Sebastian Kucharzyk Aug 07 '19 at 16:49
  • @SebastianKucharzyk Consequences of doing so most probably. – πάντα ῥεῖ Aug 07 '19 at 16:52
  • @πάνταῥεῖ, This object is supposed to be lightweight and all things needed for its configuration (i.e. registers) known during the compile time. Therefore I thought that it would be nice to make constructors of both Registers and Executor constexpr so they can be instantiated during the compile time. Do you consider this as bad practice? – Sebastian Kucharzyk Aug 07 '19 at 17:08
  • @SebastianKucharzyk It doesn't is a matter of _bad practice_ or not, that's just what you need to get it working. Also I don't understand what's your concern about _lightweight_. – πάντα ῥεῖ Aug 07 '19 at 17:14

1 Answers1

3

The function is implicitly an inline function. It should be defined in any compilation unit that calls it.

From the c++17 Standard (10.1.5 The constexpr specifier)

1 The constexpr specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template. A function or static data member declared with the constexpr specifier is implicitly an inline function or variable...

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Oh, if it's inline then linking error makes perfect sense. Thanks! – Sebastian Kucharzyk Aug 07 '19 at 16:55
  • from Moscov, why no at all? When I include Executor.h in OpcodesImmediateExecutorTests.cpp and try to instantiate it then it can see only the declaration of an inline function and its body is in a different translation unit so he cannot link it. Right? – Sebastian Kucharzyk Aug 07 '19 at 17:03
  • 1
    @SebastianKucharzyk You are right. A definition of an inline function shall be in each compilation unit where its definition is required. – Vlad from Moscow Aug 07 '19 at 17:05