LINUX.ORG.RU

ограничение специализаций шаблона

 


0

2

Что-то я торможу. Я хочу так:

Timer<uint16_t> tim; // compiles
Timer<uint32_t> tim; // compiles
Timer<anything_else> tim; // does not compile

Я так понимаю, мне надо enable_if или вручную SFINAE? Но я совсем забыл синтаксис базового класса, который должен не компилироваться и его специализации. Напомните пожалуйста

template<typename T>
class Timer {
    какой-то бред
    void F();
};

template<> Timer<uint16_t> { как убрать бред }
template<> Timer<uint32_t> { как убрать бред }

Так?

template<
        typename ResolutionType,
        typename = typename std::enable_if<
        std::is_same<uint16_t, ResolutionType>::value ||
        std::is_same<uint32_t, ResolutionType>::value,
        ResolutionType
    >::type
>
template<typename ResolutionType>
class Timer {
   void F();
}
★★

Последнее исправление: dissident (всего исправлений: 1)

Ответ на: комментарий от thunar

Зато не скомпилиируется если explicitely кто-то не вызовет конструктор с uint16_t или uint32_t.

Короче IMHO сойдет. Анонимусу которому не компелируется:

template<typename ResolutionType,
         typename std::enable_if_t<
                      std::is_same<uint16_t, ResolutionType>::value || std::is_same<uint32_t, ResolutionType>::value,
                      ResolutionType>
        >
class Timer {
public:
    Timer(ResolutionType us) 
//...

Или так лучше?

template <typename RealType>
class A
{
  static_assert(std::is_same<RealType, double>::value || std::is_same<RealType, float>::value,
                "some meaningful error message");
};

(c) https://stackoverflow.com/questions/16976720/how-do-i-restrict-a-template-class-to-certain-built-in-types/16978055

?

Второе как-то покороче вроде..

dissident ★★
() автор топика
Последнее исправление: dissident (всего исправлений: 2)
Ответ на: комментарий от dissident
std::is_same<RealType, double>::value || std::is_same<RealType, float>::value

зачем условие если ты хочешь для любых типов это делать для которых нет частичной специализации?

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Да, так короче вышло и работает:

template<typename ResolutionType>
class Timer {
    static_assert(std::is_same<ResolutionType, uint16_t>::value || std::is_same<ResolutionType, uint32_t>::value,
                  "MCU has only 16-bit or 32-bit timers");
public:
// ...

Спасибо за метод утенка, особенно злому анонимусу.

dissident ★★
() автор топика

Чтобы явно запретить специализацию я бы заюзал static_assert. Как вариант, можно реализацию шаблона убрать в .cpp, и сделать явные специализации для uint16_t и uint32_t, соответственно других специализаций не будет и из нельзя будет сделать. Но static_assert правильнее.

Ещё я бы сделал какие-нибудь более явные ограничения на тип, например is_integral, возможно ещё is_unsigned, и возможно что-то из numeric_limits.

slovazap ★★★★★
()

Буду оригинален: тебе нужно всего две функции для двух типов. Используй перегрузку, а не шаблоны.

HugeCoreDump
()

Как вариант

namespace detail_ {
template <class T> class Timer {
    какой-то бред
    void F();
};
}

template <class T> class Timer;
template<> class Timer<uint16_t> : public detail_::Timer<uint16_t> {};
template<> class Timer<uint32_t> : public detail_::Timer<uint32_t> {};
anonymous
()
Ответ на: комментарий от slovazap

Как правило делают общею реализацию, где static assert проваливается всегда, делают две специализации, реализация которых находится в cpp файле.

zerhud
()

тебе надо сделать специализацию у которой нет реализации и тогда она не соберётся (не слинкуется), затем сделать специализации у коорых есть реализация и они соберутся. а вообще у тебя неправильный дизайн если у тебя возникают такие вопросы.

Timer надо положить в анонимный неймспейс и не давать к нему доступ, а наружу выставить только Timer<uint32_t> и Timer<uint16_t>.

anonymous
()
Ответ на: комментарий от anonymous
Ответ на: комментарий от slovazap

«Как правило» так не делают - это бессмысленно когда можно сделать две перегрузки.

Какая разница куда это впихнуть - со статик ассертом все понятно, я что Александреску что ли чтобы шаблонами жонглировать? Или двумя конструкторами.. У меня же поле будет унутре (извините за java синтаксис):

class Timer<T> {
    T smth;
}

static_assert конкретно одной строчкой говорит что T должно быть такое и такое.

dissident ★★
() автор топика
Ответ на: комментарий от anonymous

тебе надо сделать специализацию у которой нет реализации и тогда она не соберётся (не слинкуется)

Если без static_assert, то мне надо сделать базовый шаблон содержащий бред. И специализации, в которых нет бреда (для uint16_t и uint32_t). Я забыл как сделать бредовый шаблон. Я давно Александреску читал. static_assert ок. Просто интересно вспомнить.

dissident ★★
() автор топика
Ответ на: комментарий от anonymous

template<> class Timer<uint16_t> : public detail_::Timer<uint16_t> {}; template<> class Timer<uint32_t> : public detail_::Timer<uint32_t> {};

А как они убирают «какой-то бред» из базового класса?

[Alexandrescu Team]

dissident ★★
() автор топика
Ответ на: комментарий от dissident

просто делаешь шаблон без реализации, если у шаблона нет реализации, то код с таким шаблоном не соберётся. но ассерт - это тоже ок, не обязательно делать как александреску. потом заменишь ассерт на концепцию и всё, заменишь одну строчку на другую. можешь даже сразу сделать #if __cpp_concepts - напиши концепцию, иначе вкорячь ассерт.

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

anonymous
()
Ответ на: комментарий от anonymous

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

C++ у меня 2014 года розлива в Eclipse (STM32CubeIDE). Интересно там можно более новые кресты выбрать?

dissident ★★
() автор топика
Ответ на: комментарий от anonymous

template<> class Timer<uint16_t> : public detail_::Timer<uint16_t> {};

Дошло. Наследнитки Timer<uint16_t> и Timer<uint32_t> явно объявлены без бреда и используются в коде, поэтому их код генерируется из их шаблонов. Внутри же template class Timer какая-то бодяга.

А нет. Не работает:

#include <cstdint>
using namespace std;

template <class T> class Timer {
    какой-то бред
    void F();
};

template <class T> class Timer;
template<> class Timer<uint16_t> : Timer<uint16_t> {};
template<> class Timer<uint32_t> : Timer<uint32_t> {};

int main(int argc, char *argv[]) {
    Timer timer<uint16_t> t16; // compiles
    Timer timer<uint32_t> t32; // compiles
//    Timer timer<char> t8; // does not compile    
}

Не собирается.

~/tmp >>> g++ tmpl.cpp 
tmpl.cpp:5:5: error: ‘какой’ does not name a type
    5 |     какой-то бред
      |     ^~~~~
tmpl.cpp:10:36: error: invalid use of incomplete type ‘class Timer<short unsigned int>’
   10 | template<> class Timer<uint16_t> : Timer<uint16_t> {};
      |                                    ^~~~~~~~~~~~~~~
tmpl.cpp:10:18: note: declaration of ‘class Timer<short unsigned int>’
   10 | template<> class Timer<uint16_t> : Timer<uint16_t> {};
      |                  ^~~~~~~~~~~~~~~
tmpl.cpp:11:36: error: invalid use of incomplete type ‘class Timer<unsigned int>’
   11 | template<> class Timer<uint32_t> : Timer<uint32_t> {};
      |                                    ^~~~~~~~~~~~~~~
tmpl.cpp:11:18: note: declaration of ‘class Timer<unsigned int>’
   11 | template<> class Timer<uint32_t> : Timer<uint32_t> {};
      |                  ^~~~~~~~~~~~~~~
tmpl.cpp: In function ‘int main(int, char**)’:
tmpl.cpp:14:11: error: missing template arguments before ‘timer’
   14 |     Timer timer<uint16_t> t16; // compiles
      |           ^~~~~
tmpl.cpp:15:11: error: missing template arguments before ‘timer’
   15 |     Timer timer<uint32_t> t32; // compiles
dissident ★★
() автор топика
Последнее исправление: dissident (всего исправлений: 4)
Ответ на: комментарий от dissident

https://godbolt.org/z/En9rEb

это по александреску, но я тебе ещё раз скажу на всякий случай, что не обязательно делать как александреску. александреску придумал прикольные хаки, но лучше спрятать таймер, а наружу выставить только таймеры для uint16_t и uint32_t.

как тебе другой анон написал: положи их в detail чтобы их никто не трогал, и сделай typedef не в detail на два типа таймеров, вот и всё.

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

anonymous
()
Ответ на: комментарий от anonymous

Тогда внутри класса Timer надо будет хранить поле как uint32_t и для 16-битного и для 32-битного таймера. А с параметром шаблона и static_assert тип этого поля будет или uint16_t или uint32_t, что как-то IMHO логичнее.

dissident ★★
() автор топика
Ответ на: комментарий от dissident

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

вообще ты можешь специализировать шаблон на числах, а не на типах: https://godbolt.org/z/3Weenc

но вполне возможно, что это переусложнение. если тебя устраивает такой феншуй с ассертом, то покатит. тем более, что в с++20 это будет идиоматично если использовать requires.

просто за код в стиле александреску тебе мало кто будет благодарен, поэтому возможно, что чем проще - тем лучше.

anonymous
()
Ответ на: комментарий от anonymous

int8_t может быть сконвертировано в uint16_t без ограничений и какой это может дать побочный эффект в данном случае - не понятненько.

Надо будет explicitely делать static_cast, иначе template deduction не сработает.

PS С числами красиво, спасибо.

anonymous
()
Ответ на: комментарий от anonymous

Надо будет explicitely делать static_cast, иначе template deduction не сработает.

да, но что если делается decltype(x) и всё работает, но потом у x меняется тип и внезапно тип меняется и у таймера. тут имхо проблема в том, что это всё делается наоборот: типа если uint16_t, то разрешение таймера должно быть 16 бит, если uint32_t, то 32 бита. а должно быть наоборот: если разрешение 16 бит, то тип должен быть uint16_t, если 32 бита, то uint32_t.

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

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.