Использование шаблона переменной внутри встроенной функции constexpr без раскрытия шаблона переменной?

Можно ли использовать шаблон переменной внутри встроенной функции constexpr без раскрытия самого шаблона переменной?

Например, это компилируется и работает:

template<typename T> constexpr T twelve_hundred = T(1200.0);

template<typename T>
inline constexpr T centsToOctaves(const T cents) {
    return cents / twelve_hundred<T>;
}

Но это не компилируется:

template<typename T>
inline constexpr T centsToOctaves(const T cents) {
    template<typename U> constexpr U twelve_hundred = U(1200.0);
    return cents / twelve_hundred<T>;
}

Причина, по-видимому, в том, что объявления шаблонов не разрешены в области блока (GCC выдает информативное сообщение об ошибке, а Clang - нет).

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

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


person Danra    schedule 15.02.2017    source источник
comment
Подумайте, что лучшее, что вы можете сделать, это сделать его частным (статическим) членом класса, который дружит с вашей свободной функцией. Тем не менее, я не считаю, что повторение и шаблон стоит того, чтобы просто поместить его в подробное пространство имен. Это очень хорошо зарекомендовавшее себя соглашение, а C++ на самом деле не тот язык, где вы все равно можете сделать невозможным плохое поведение.   -  person Nir Friedman    schedule 15.02.2017


Ответы (2)


Из стандарта имеем следующее:

Объявление шаблона – это объявление. [...]. Объявление, представленное объявлением шаблона переменной, является шаблоном переменной. [...]

И:

Объявление шаблона может отображаться только как объявление области пространства имен или области видимости класса.

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

class C {
    template<typename T>
    static constexpr T twelve_hundred = T(1200.0);

public:
    template<typename T>
    static constexpr T centsToOctaves(const T cents) {
        return cents / twelve_hundred<T>;
    }
};

int main() {
    C::centsToOctaves(42);
}

Другое возможное решение:

class C {
    template<typename T>
    static constexpr T twelve_hundred = T(1200.0);

    template<typename T>
    friend inline constexpr T centsToOctaves(const T cents);
};

template<typename T>
inline constexpr T centsToOctaves(const T cents) {
    return cents / C::twelve_hundred<T>;
}

int main() {
    centsToOctaves(42);
}

У него есть плюс, что centsToOctaves больше не является функцией-членом C, как упоминалось в комментариях.

При этом я не понимаю, что мешает вам просто сделать это:

template<typename T>
inline constexpr T centsToOctaves(const T cents) {
    return cents / T{1200};
}
person skypjack    schedule 15.02.2017
comment
Да, я написал это выше. Вопрос заключался в том, есть ли способ не раскрывать шаблон переменной. - person Danra; 15.02.2017
comment
Как я писал в другом ответе, не следует предполагать, что вместо этого можно сделать бесплатную функцию статической функцией класса, это имеет довольно широкие последствия. - person Nir Friedman; 15.02.2017
comment
@NirFriedman Ваш комментарий касается встраивания функции, и мне кажется, что это было ожиданием ОП. Во всяком случае, сделать его другом может быть обходным путем. Позвольте мне обновить ответ. - person skypjack; 15.02.2017
comment
Извините, вы правы, это потому, что я начал писать комментарий, а потом они перешли на пересылку, и я изменил свой комментарий. Тем не менее, субстанция остается в силе: свободная функция ведет себя совершенно иначе, чем статическая функция, которую невозможно согласовать. - person Nir Friedman; 15.02.2017
comment
Проголосовал за, так как я думаю, что это лучший возможный ответ, который может соответствовать первоначальным требованиям. - person Nir Friedman; 15.02.2017
comment
@skypjack При этом я не понимаю, что мешает вам просто сделать это, я собирался написать, что Clang предупредит вас о сужении double до float, так как это то, что я видел, когда пытался это сделать, но я могу' больше не увижу. Я обновлю, если увижу это снова, может быть, это была моя случайность. - person Danra; 15.02.2017
comment
@Danra Он вернется, например, с использованием T{1200.0} и int как T. Позвольте мне обновить ответ немного другой версией, которая должна работать в обоих случаях. - person skypjack; 15.02.2017
comment
@skypjack Хм, нет ли риска потери точности при инициализации double (в случае T=double) из int таким образом? - person Danra; 15.02.2017
comment
@Danra 1200 прекрасно представляется как double, а double{1200} просто отлично, как и int{1200}. - person skypjack; 15.02.2017
comment
@skypjack Конечно, я имел в виду более общую проблему инициализации double с помощью int таким образом. - person Danra; 15.02.2017

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

class Detail {
 public:
  template<typename T>
  static constexpr T centsToOctaves(const T cents) {
    return cents / twelve_hundred<T>;
  }

 private:
  template<typename U>
  static constexpr U twelve_hundred = U(1200.0);
};

// forwarding
template<typename T>
inline constexpr T centsToOctaves(const T cents) {
  return Detail::centsToOctaves<T>(cents);
}

int main() {
  centsToOctaves<int>(12);
  return 0;
}

Не связанные:

Вам может не понадобиться объявлять переменную шаблона constexpr. Поскольку вы не можете изменить его после инициализации, альтернативная реализация может использовать литерал напрямую:

template<typename T>
inline constexpr T centsToOctaves(const T cents) {
    using U = T;
    return cents / U(1200.0);
}

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

template <>
inline constexpr int centsToOctaves(const int cents) {
    using U = int;
    return cents / U(1200.0);
}

Но, к сожалению, это решение будет генерировать дублированный код, может быть, даже хуже.

person felix    schedule 15.02.2017
comment
Встроенная статическая функция класса подразумевается и является избыточной. Кроме того, не знаете, почему вы должны переслать, а не просто объявить друга? - person Nir Friedman; 15.02.2017
comment
Действительно, это решение, но, к сожалению, не самое лучшее, если встраивание функции в класс будет громоздким (как в моем случае). - person Danra; 15.02.2017
comment
@NirFriedman Сначала я объявляю функцию-член, а затем понимаю, что centsToOctaves не является функцией-членом, поэтому в тот момент я подумал, что лучше использовать функцию-член для доступа к частной переменной-члену. Тогда без дальнейших размышлений... вы знаете, что произошло. - person felix; 15.02.2017