Инициализировать статический член массива constexpr с условным оператором в шаблоне класса - PullRequest
0 голосов
/ 21 октября 2018

Рассмотрим минимальный пример

#include <iostream>

template<typename T>
struct foo
{
    // won't compile. how to change?
    static constexpr char sep[3] = std::is_integral<T>::value ? ". " : ", ";

    // many other things ...
};

int main()
{
    std::cout << foo<int>::sep << std::endl;     // prints .
    std::cout << foo<double>::sep << std::endl;  // prints ,
}

Что я хочу достичь:

  • , если T имеет целочисленный тип, тогда sep инициализируется как .
  • в противном случае sep инициализируется как ,

Однако компилятор не допустит этого, говоря:

error: array must be initialized with a brace-enclosed initializer

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

Мой вопрос: Есть ли что-нибудь, что я могу сделать для достижения этой цели?

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

Большое спасибо.

Ответы [ 2 ]

0 голосов
/ 21 октября 2018

Лучший способ - использовать базовый класс со специализацией и поместить sep в базовый класс:

template <bool IsIntegral>
struct foo_base;

template<>
struct foo_base<true>
{
    static constexpr char sep[3] = ". ";
};

template<>
struct foo_base<false>
{
    static constexpr char sep[4] = ",  ";
};


template<typename T>
struct foo : foo_base<std::is_integral_v<T>>
{
    // many other things ...
};

Но если вы не хотите, чтобы другие получали доступ к базе, вы можете использовать приватныйНаследование:

template<typename T>
struct foo : private foo_base<std::is_integral_v<T>>
{
    using foo_base<std::is_integral_v<T>>::sep;
    // many other things ...
};

Редактировать

Преимущество этого решения по сравнению с использованием std::array<char, 3> заключается в том, что это решение прекрасно работает с функциями, которые принимают ссылку на массивы Си char.Ни хранилище const char*, ни std::array<char, 3> не имеют такой возможности.

Например, если у вас есть такие функции, как:

template <std::size_t I>
constexpr int count_nuls(const char (&x)[I])
{
    // Can't use std::count, since it is not constexpr
    unsigned count = 0;
    for (auto ch: x)
        if (ch == '\0')
            ++count;
    return count;
}

Эту функцию нельзя использовать с std::array илис const char *.Если таких функций много, возможно, вы не захотите обновить их все до std::array.Например, эта функция отлично работает в:

static constexpr unsigned nuls = count_nuls(foo<double>::sep);

, но не будет работать (без дальнейшей модификации) с std::array<char, 3>.

0 голосов
/ 21 октября 2018

C-массивы не могут быть скопированы, поэтому вы должны обойти это

  • Выполните проверку для каждого символа:

    constexpr char sep[3] = { std::is_integral<T>::value ? '.' : ',', ' ', '\0' };
    
  • Не используйте массив, но указатель (поэтому вы теряете размер):

    constexpr const char* sep = std::is_integral<T>::value ? ". " : ", ";
    
  • Используйте std::array:

    constexpr std::array<char, 3> sep = std::is_integral<T>::value
       ? std::array<char, 3>{{'.', ' ', 0}}
       : std::array<char, 3>{{',', ' ', 0}};
    
  • Используйте ссылку на массив:

    constexpr char dot_sep[3] = std::is_integral<T>::value ? ". " : ", ";
    constexpr char comma_sep[3] = std::is_integral<T>::value ? ". " : ", ";
    constexpr const char (&sep)[3] = std::is_integral<T>::value ? dot_sep : comma_sep;
    

    и предоставьте определение dot_sep / comma_sep, которые используются ODR.

...