MSVC не удается специализировать член шаблонного класса, определенного в отдельном единичном переводе.

Вопрос или проблема

Я пытался собрать VTK 9.3.1 как статическую библиотеку с помощью Visual Studio 17 2022, но это не удается на этапе линковки:

vtkCommonCore-9.3.lib(vtkSMPToolsAPI.obj) : error LNK2019: unresolved external symbol "public: bool __cdecl vtk::detail::smp::vtkSMPToolsImpl<1>::IsParallelScope(void)" (?IsParallelScope@?$vtkSMPToolsImpl@$00@smp@detail@vtk@@QEAA_NXZ) referenced in function "public: bool __cdecl vtk::detail::smp::vtkSMPToolsAPI::IsParallelScope(void)" (?IsParallelScope@vtkSMPToolsAPI@smp@detail@vtk@@QEAA_NXZ)

(Примечание: как динамическая библиотека, она собирается нормально.)

Я обнаружил, что эта проблема уже была сообщена, например, здесь, но она остается нерешенной.

Я упростил проблему до следующего MRE (перенесено в конец поста для лучшей читаемости).

При установленном стандарте C++17 MinGW компилирует код без проблем, в то время как MSVC выдает ту же ошибку линковки:

main.obj : error LNK2019: unresolved external symbol "public: unsigned __int64 __cdecl Brick<0>::GetNumberOfSides(void)" (?GetNumberOfSides@?$Brick@$0A@@@QEAA_KXZ) referenced in function main

Мне кажется, что по какой-то причине компилятор не может записать определение Brick< ShapeType::Circle >::GetNumberOfSides в объектный файл Circle.obj, поэтому линковщик не может его найти.

У меня вопрос: Является ли приведенный выше минимальный пример корректным согласно стандарту C++17?

Другими словами, является ли это ошибкой в компиляторе Microsoft или MinGW (GCC) слишком снисходителен?

Примечание: MSVC компилирует код правильно, когда установлен стандарт C++20.

Brick.h:

#ifndef Brick_h
#define Brick_h

#include <cstddef>


enum class ShapeType
{
    Circle   = 0,
    Triangle = 3,
    Square   = 4
};


template< ShapeType Shape >
class Brick
{
public:
    std::size_t GetNumberOfSides() 
    { 
        return static_cast< std::size_t >( Shape );
    }
};

#endif // Brick_h

Circle.cxx:

#include "Brick.h"


template<>
std::size_t Brick< ShapeType::Circle >::GetNumberOfSides()
{
    throw 0;
}

main.cxx:

#include <iostream>

#include "Brick.h"


template<>
std::size_t Brick< ShapeType::Circle >::GetNumberOfSides();

int main()
{
    Brick< ShapeType::Circle > B;
    
    try
    {
        std::cout << B.GetNumberOfSides();
    }
    catch( ... )
    {
        std::cerr << "Необработанное исключение - завершение. ";
    }
    
    return 0;
}

CMakeLists.txt:

cmake_minimum_required( VERSION 3.12...3.29 FATAL_ERROR )

project( Toy VERSION 1.0 )

set( CMAKE_CXX_STANDARD 17 )
set( CMAKE_CXX_STANDARD_REQUIRED True )

add_executable( Toy main.cxx Circle.cxx )

target_include_directories( Toy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} )

Команды сборки:

mkdir build
cd build
cmake ..
cmake --build .

Ответ или решение

Ваш вопрос о проблемах компиляции с использованием MSVC (Microsoft Visual C++) и специализированных классов шаблонов, определённых в отдельном переводном единице, требует внимания к нескольким ключевым аспектам стандартов C++ и особенностей компиляторов.

Проблема

Вы столкнулись с ошибкой компоновки, связанной с неопределённым внешним символом для метода GetNumberOfSides в классе шаблона Brick. Это происходит, когда вы используете шаблоны и не обеспечиваете корректную спецификацию для методов шаблонных классов в реальных единицах переводов.

Код и Содержимое Файлов

  1. Brick.h

    #ifndef Brick_h
    #define Brick_h
    
    #include <cstddef>
    
    enum class ShapeType
    {
       Circle   = 0,
       Triangle = 3,
       Square   = 4
    };
    
    template< ShapeType Shape >
    class Brick
    {
    public:
       std::size_t GetNumberOfSides() 
       { 
           return static_cast<std::size_t>(Shape);
       }
    };
    
    #endif // Brick_h
  2. Circle.cxx

    #include "Brick.h"
    
    template<>
    std::size_t Brick< ShapeType::Circle >::GetNumberOfSides()
    {
       throw 0;
    }
  3. main.cxx

    #include <iostream>
    #include "Brick.h"
    
    template<>
    std::size_t Brick< ShapeType::Circle >::GetNumberOfSides();
    
    int main()
    {
       Brick< ShapeType::Circle > B;
    
       try
       {
           std::cout << B.GetNumberOfSides();
       }
       catch( ... )
       {
           std::cerr << "Uncaught exception - terminating. ";
       }
    
       return 0;
    }

Проблема Специализации

Ошибка связывается с тем, что MSVC не может найти определение GetNumberOfSides для классов шаблона Brick<ShapeType::Circle>. Главная причина этих ошибок заключается в том, что специализированные методы шаблонов должны быть определены в том же переводном единице, где они используются, так как компилятору нужно иметь доступ к их реализации на этапе линковки.

Почему это Работает в C++20?

При переходе на C++20 произошли изменения в правилах спецификации и определения методов шаблонов, что могло позволить MSVC правильно обрабатывать такие случаи и тем самым убирает проблему во время линковки. Однако, по стандартам C++17, такая спецификация приводит к проблемам, как вы заметили.

Как Исправить

Чтобы исправить вашу проблему, необходимо обеспечить, чтобы определение функционала шаблона было доступно компилятору на этапе линковки. Это можно сделать двумя способами:

  1. Перенести Определение Метода в Заголовочный Файл
    Включите определение метода GetNumberOfSides в файл Brick.h непосредственно, что позволит компилятору всегда видеть его.

    std::size_t GetNumberOfSides() 
    { 
       return static_cast<std::size_t>(Shape);
    }
  2. Использовать extern в Заголовочном Файле
    Убедитесь, что в Circle.cxx метод определяется именно в том месте, где он должен быть доступен:

    template<>
    std::size_t Brick< ShapeType::Circle >::GetNumberOfSides();

Заключение

Исходя из вышесказанного, ваша проблема с линковкой сообщений в MSVC – это следствие ограничений C++17 в контексте спецификации шаблонов. Это не является багом компилятора Microsoft, а результатом различий в стандартах C++ и реализации шаблонов в различных компиляторах. Чтобы избежать подобных проблем, старайтесь держать определения шаблонов и их специализаций в тех же единицах переводов, где они применяются.

Оцените материал
Добавить комментарий

Капча загружается...