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

Рассмотрим следующий пример, состоящий из 4 файлов.



Внешний.h

#pragma once

#include "genericAlgorithm.h"

class Outer
{
private:
    struct Inner {};    // Note that Inner is private

    const Inner inner;

public:
    Outer() : inner() {}

    inline void method()
    {
        genericAlgorithm(inner);
    }
};


genericAlgorithm.h

#pragma once

template <typename T>
void genericAlgorithm(const T& value);


genericAlgorithm.cpp

#include "genericAlgorithm.h"

#include "Outer.h"

template <typename T>
void genericAlgorithm(const T& value) {}

// Explicit template instantiation (compiles on GCC, Clang; error C2248 on MSVC)
template void genericAlgorithm<Outer::Inner>(const Outer::Inner& value);


main.cpp

#include "Outer.h"

int main()
{
    Outer outer;
    outer.method();
    return 0;
}

Как видите, в genericAlgorithm.cpp есть явное создание экземпляра шаблона функции genericAlgorithm() для аргумента Outer::Inner, который является закрытой внутренней структурой класса Outer.

Насколько я понимаю, это законно, поскольку согласно cppreference.com...

Определения явного создания экземпляров игнорируют спецификаторы доступа к членам: типы параметров и возвращаемые типы могут быть закрытыми.

И на самом деле этот код отлично компилируется в GCC 6.3 и Clang 4.0.

Однако MSVC (Visual Studio 2017 15.2), похоже, не справляется с этим и выдает следующую ошибку компиляции:

genericalgorithm.cpp(9): error C2248: 'Outer::Inner': cannot access private struct declared in class 'Outer'

Итак, это ошибка в MSVC или я что-то упускаю и на самом деле есть проблема с моим кодом, которую нужно исправить? Если да, значит ли это, что GCC и Clang, а также cppreference.com ошибаются?


ОБНОВЛЕНИЕ: я считаю, что нашел соответствующий отрывок в §14.7.2 [templ.explicit] (пункт 12) n4296 и n4567 рабочие проекты стандарта:

Обычные правила проверки доступа не применяются к именам, используемым для указания явных экземпляров. [Примечание. В частности, аргументы и имена шаблонов, используемые в деклараторе функций (включая типы параметров, возвращаемые типы и спецификации исключений), могут быть частными типами или объектами, которые обычно недоступны, а шаблон может быть шаблоном-членом или функцией-членом. который обычно недоступен. — примечание в конце]

Если я не ошибаюсь в том, что написано, кажется, что такое поведение MSVC действительно не соответствует требованиям. Конечно, это всего лишь черновики — у меня, к сожалению, нет доступа к фактическим 133 доллара США за копию стандартно, поэтому я не могу быть уверен, что этот элемент сохранен.


person TerraPass    schedule 06.11.2017    source источник


Ответы (1)


Я бы прочитал это как означающее, что определение явного создания экземпляра игнорирует спецификаторы доступа в веще, которое вы явно указываете. Это не волшебным образом предоставляет вам доступ к закрытым членам других элементов.

Тип «Внутренний» является приватным для того, что вы используете в качестве аргумента, а не для типа genericAlgorithm.

person Joe    schedule 06.11.2017
comment
Итак, вы говорите, что и GCC, и Clang ошиблись, поскольку они компилируют данный пример без каких-либо проблем? - person TerraPass; 06.11.2017
comment
Я признаю, что хотел бы написать свой ответ в виде комментария, так как это было больше моим предположением, чем уверенностью. Для меня не имеет смысла, что вам будет предоставлен доступ к закрытым членам других типов только потому, что вы специализируетесь на шаблоне. Но мне хорошо известно, что GCC, как правило, лучше соответствует стандартам, чем VC. - person Joe; 06.11.2017
comment
Однако я явно создаю экземпляр, а не специализирую шаблон. Вариант использования, где я действительно столкнулся с этой проблемой, был в проекте, где для сокращения времени (повторной) компиляции некоторые реализации шаблонов были перемещены в .cpp, а соответствующие шаблоны были созданы явно. Один из этих экземпляров включал закрытый тип, как в этом примере. Мне показалось очень полезным игнорировать спецификаторы доступа в этом случае, потому что альтернативой было бы вторгнуться в инкапсуляцию класса, содержащего частный тип, сделав его общедоступным или добавив шаблон в качестве друга. - person TerraPass; 06.11.2017
comment
Обратите внимание, что несмотря на то, что шаблон был неявно создан, не нужно было делать никаких уступок для снятия ограничений доступа. Поэтому для меня имеет смысл, что явное создание экземпляра не должно создавать дополнительных препятствий по сравнению с неявным. Поэтому у меня есть сильное подозрение, что в данном случае MSVC не соответствует требованиям. - person TerraPass; 06.11.2017
comment
Позвольте мне убедиться, что я понимаю вас. Вы говорите, что когда genericAlgorithm (или что-то, что он означает в вашем реальном проекте) был просто шаблоном в заголовочном файле, вы фактически могли написать его так, чтобы он принимал аргумент, который был закрытым типом, объявленным в каком-то другом, несвязанном класс? А что скомпилировано? - person Joe; 06.11.2017
comment
Точно. И это тоже имело смысл: не требовалось никаких изменений в классе, который на самом деле использовал этот шаблон: точно так же, как ранее он неявно создавал экземпляр указанного шаблона со своим собственным закрытым типом, так что теперь он мог использовать это воплощение без его инкапсуляции. быть скомпрометирован. - person TerraPass; 06.11.2017
comment
О, вы имеете в виду до того, как он был явно создан? Тогда тем более да. :) Я имею в виду, что если бы использование частных типов в качестве аргументов типа шаблона было вообще запрещено, вы не смогли бы, например, использовать std::vector<Outer::Inner> в качестве одного из полей Outer, что было бы абсурдно. - person TerraPass; 06.11.2017
comment
Использование закрытых типов в качестве аргументов шаблона не запрещено, если вы соблюдаете правила... конфиденциальности. То есть совершенно нормально объявить std::vector‹Outer::Inner› из некоторой функции-члена класса Outer. Но недопустимо объявлять его вне класса. Функция шаблона вне класса, которая делает это, также не должна быть допустимой. Ваша функция шаблона не только использует Outer::Inner в качестве аргумента шаблона (что допустимо), но также принимает Outer::Inner в качестве аргумента функции, что неправильно - person Joe; 07.11.2017
comment
Давайте продолжим обсуждение в чате. - person TerraPass; 07.11.2017