Конструктор Constexpr, определенный в файле .cpp, вызывает ошибку связывания

У меня проблема с связыванием конструктора constexpr между двумя проектами в одном решении Visual Studio.

У меня есть два проекта в моем решении Visual Studio 2019:

  • NES_Core
  • NES_Core_Tests

Первый — проект .lib, второй — базовый проект GTest. Оба используют C++17.

У меня есть следующий класс, объявленный в executor.h в 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_;
    };
}

И определение в executor.cpp:

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

Позже я пытаюсь создать объект Executor в OpcodesImmediateExecutorTests.cpp в проекте NES_Core_Tests:

#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_;
};

К сожалению, не удается связать:

OpcodesImmediateExecutorTests.obj: ошибка LNK2019: неразрешенный внешний символ "public: __thiscall nes::cpu::opcodes::immediate::Executor::Executor(struct nes::cpu::registers::Registers &)" (??0Executor@ непосредственный@opcodes@cpu@nes@@QAE@AAURegisters@registers@34@@Z), указанный в функции "public: __thiscall OPCodes_::OPCodes_(void)" (??0OPCodes_@@QAE@XZ)

Более того, когда я удаляю ключевое слово constexpr из .h и .cpp, связывание выполняется просто отлично. У вас есть идеи, почему это могло произойти?


person Sebastian Kucharzyk    schedule 07.08.2019    source источник
comment
Почему вы сделали конструктор constexpr? Вы понимаете последствия этого?   -  person Lightness Races in Orbit    schedule 07.08.2019
comment
Как вы думаете, зачем вам нужно так много пространств имен?   -  person    schedule 07.08.2019
comment
@LightnessRacesinOrbit, не могли бы вы уточнить, что вы подразумеваете под разветвлениями?   -  person Sebastian Kucharzyk    schedule 07.08.2019
comment
@SebastianKucharzyk Последствия этого, скорее всего.   -  person πάντα ῥεῖ    schedule 07.08.2019
comment
@ πάνταῥεῖ, этот объект должен быть легким, и все, что необходимо для его настройки (например, регистры), известно во время компиляции. Поэтому я подумал, что было бы неплохо сделать конструкторы как регистров, так и исполнителя constexpr, чтобы их можно было создавать во время компиляции. Вы считаете это плохой практикой?   -  person Sebastian Kucharzyk    schedule 07.08.2019
comment
@SebastianKucharzyk Дело не в плохой практике или нет, это просто то, что вам нужно, чтобы заставить это работать. Также я не понимаю, что вас беспокоит по поводу легкости.   -  person πάντα ῥεῖ    schedule 07.08.2019


Ответы (1)


Функция неявно является встроенной функцией. Он должен быть определен в любой единице компиляции, которая его вызывает.

Из стандарта С++ 17 (10.1.5 Спецификатор constexpr)

1 Спецификатор constexpr должен применяться только к определению переменной или шаблона переменной или к объявлению функции или шаблона функции. Функция или статический элемент данных, объявленный с помощью спецификатора constexpr, неявно является встроенной функцией или переменной...

person Vlad from Moscow    schedule 07.08.2019
comment
О, если он встроенный, то ошибка связывания имеет смысл. Спасибо! - person Sebastian Kucharzyk; 07.08.2019
comment
@SebastianKucharzyk Совсем нет. Добро пожаловать.:) - person Vlad from Moscow; 07.08.2019
comment
из Москвы, почему нет? Когда я включаю Executor.h в OpcodesImmediateExecutorTests.cpp и пытаюсь его создать, он может видеть только объявление встроенной функции, а его тело находится в другой единице перевода, поэтому он не может связать его. Верно? - person Sebastian Kucharzyk; 07.08.2019
comment
@SebastianKucharzyk Вы правы. Определение встроенной функции должно быть в каждой единице компиляции, где требуется ее определение. - person Vlad from Moscow; 07.08.2019