Декларация не решает ошибку «явная специализация после создания»

Предположим, я пытаюсь создать собственную реализацию boost :: filesystem :: path, используя Любопытно повторяющийся шаблон:

(Для краткости приведен код, но он будет содержать проблему, указанную при компиляции сg++ -std=c++11 -o mypath ./mypath.cpp‘, используя GCC 4.8.4)

mypath.hpp:

#ifndef MYPATH_HPP
#define MYPATH_HPP

#include <string>
#include <vector>

namespace my {

template <class T>
class PathBase
{
public:
PathBase();
PathBase(std::string const& p);

std::string String() const;

bool IsSeparator(char c) const;
std::string Separators() const;

typedef std::vector<std::string> pathvec;

protected:
pathvec _path;

private:
virtual std::string _separators() const =0;
};class Path : public PathBase<Path>
{
public:
Path();
Path(std::string const& p);

private:
virtual std::string _separators() const final;
};

} // namespace 'my'

#endif // MYPATH_HPP

mypath.cpp:

#include "mypath.hpp"
namespace my {

//////////template class PathBase<Path>;

template<>
bool PathBase<Path>::IsSeparator(char c) const
{
return (Separators().find(c) != std::string::npos);
}

template <>
std::string PathBase<Path>::Separators() const
{
return _separators();
}

} // namespace

int main(int argc, char** argv)
{
return 0;
}

Конечно, я обнаружил, что написанный код не будет компилироваться, поскольку я явно специализируюсь Separators() после IsSeparator() неявно инстанцировал это. Но я не особенно хочу играть в биву, пытаясь держать все мои методы в выгодном порядке.

Исследуя подобные вопросы по SO, я обнаружил, что это принятый ответ один из них предположил, что я могу решить эту проблему аккуратно, просто объявив о своей специализации. Но…

  1. Мой закомментированный template class PathBase<Path>; строка в mypath.cpp не оказала влияния на проблему, и
  2. Такое ощущение, что мой заголовочный файл уже объявляет явную специализацию со всей class Path : public PathBase<Path> { ... } декларация.

Как должна выглядеть моя явная декларация?

3

Решение

Давайте сначала уберем их с пути:

  1. template class PathBase<Path>; не объявляет явную специализацию; это явное определение экземпляра. Вы запрашиваете создание экземпляра компилятора PathBase<Path> и все его члены, для которых у него есть определения, основанные на определениях, которые вы предоставили до этого момента. В этом конкретном случае это не имеет никакого значения действительно.

    Объявление явной специализации будет выглядеть так template<> class PathBase<Path>;но это не то, что вы хотите здесь тоже; увидеть ниже.

  2. Использование PathBase<Path> при определении Path также не объявляет явную специализацию; это вызывает неявная реализация из PathBase<Path>на основе определения, которое вы предоставили выше. Неявное создание экземпляра для шаблона класса создает определение класса и только объявления его функций-членов; он не пытается создавать экземпляры определений функций; они создаются только при необходимости, позже.


В вашем файле cpp вы явно специализируете IsSeparator а также Separators для неявно созданного экземпляра PathBase<Path>, Вы запрашиваете создание экземпляра компилятора PathBase<Path> на основе предоставленного вами общего определения, но, когда нужны определения этих конкретных функций, используйте предоставленные вами конкретные определения.

По сути, это краткая альтернатива явной специализации всего шаблона класса, когда структура класса и большинство общих определений для членов хороши, а вам нужно только настроить определения нескольких членов. Если вы явно специализировали весь шаблон класса, вам нужно было бы предоставить отдельное определение класса и определения для всех функций-членов специализации, что означало бы ненужное копирование-вставку.

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

Самое безопасное место для этого — сразу после закрывающей скобки определения template <class T> class PathBase, Что-то вроде:

class Path;
template<> std::string PathBase<Path>::Separators() const;
template<> bool PathBase<Path>::IsSeparator(char c) const;

Вам определенно нужно сделать это в файле заголовка, а не в файле cpp, иначе другие файлы cpp, которые используют заголовок, не будут знать о явных специализациях и будут пытаться создавать экземпляры универсальных версий (если они им нужны). Это сделает вашу программу плохо сформированной, диагностика не требуется (это относится и к вашему примеру). Это означает следующее: если компилятор достаточно умен, чтобы диагностировать проблему, вы должны быть благодарны; если это не так, вы не можете жаловаться, и это по-прежнему ваша вина.

Объявив явные специализации заранее, определения могут появиться позже, возможно, в отдельном файле cpp; это нормально, как и для обычных функций.

Также обратите внимание, что, если вы хотите включить определения для явных специализаций в файл заголовка (например, для облегчения встраивания), вам придется объявить их inline, опять же, как для нормальных функций. В противном случае, включение заголовка в несколько файлов cpp сделает программу плохо сформированной, NDR (как правило, вы получите несколько ошибок определения во время компоновки).


Обязательная стандартная цитата из [Temp.expl.spec] / 7:

[…] При написании специализации будьте осторожны с ее местоположением; или же
сделать его будет таким испытанием, чтобы разжечь его
самосожжения.

Да, члены комитета по стандартизации тоже люди.

4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]