Использование шаблона переменной внутри встроенной функции 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 - нет).

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

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

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};
}

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

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;
}

Unrelated:

Возможно, вам не нужно объявлять шаблон 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);
}

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

Другие вопросы по тегам