Проблемы с привязкой при использовании статического constexpr в качестве аргумента конструктора по умолчанию

#c #c 11

#c #c 11

Вопрос:

Я хотел бы, чтобы кто-нибудь разъяснил, в каких случаях безопасно использовать static constexpr в качестве аргумента по умолчанию для конструктора класса. Чтобы точно определить, что происходит, рассмотрим следующий код:

 #include <array>
#include <iostream>

struct Bar {

    using Option = size_t;
    using Options = std::array<Option, 0>;
    static constexpr Option default_option = 8080;
    static constexpr Options default_options{};

    //template <typename OptionT = Options>
    Bar(
        Options options = default_options,
        Option  option  = default_option  
       ){
        std::cout << "Constructed with option " << option << std::endl;
    }
};

int main() {
    Bar bar;
}
  

Этот код, похоже, компилируется, но не связывается. В частности, при компиляции с GCC 6.3 я получаю

 prog.cc:(.text 0x13): undefined reference to `Bar::default_options'
collect2: error: ld returned 1 exit status
  

Однако, если мы закомментируем строку-нарушитель, тогда код компилируется, связывается и выполняется правильно. Таким образом, предположительно, нет проблем с использованием static constexpr size_t в качестве аргумента по умолчанию:

 #include <array>
#include <iostream>

struct Bar {

    using Option = size_t;
    using Options = std::array<Option, 0>;
    static constexpr Option default_option = 8080;
    static constexpr Options default_options{};

    //template <typename OptionT = Options>
    Bar(
        //Options options = default_options,
        Option  option  = default_option  
       ){
        std::cout << "Constructed with option " << option << std::endl;
    }
};

int main() {
    Bar bar;
}
  

Может ли кто-нибудь объяснить мне, почему привязка работает для size_t , но не для array любого из них?

Я знаю, что я мог бы определить встроенные параметры по умолчанию следующим образом:

 Bar(
    Options options = std::array<Option, 0>{},
    Option  option  = default_option  
   ){
    std::cout << "Constructed with option " << option << std::endl;
}
  

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

Комментарии:

1. Вы создаете как C 14?

2. Этого не произойдет. C 17 бы. Просто попросили добавить соответствующие теги.

3. GCC 6.3 не поддерживает C 17 полностью. В GCC 7.1 это работает так, как задумано wandbox.org/permlink/7T2gdl4uoayhJn1Z

4. Кстати, как вы получили постоянную ссылку на Wandbox? Я также приводил там пример, но мне так и не удалось выяснить, как сгенерировать постоянную ссылку

5. После того, как вы нажмете «выполнить», над панелью вывода появится опция «поделиться». Нажатие на это дает вам привязку «URL» с постоянной ссылкой и опцию «поделиться» в Twitter 🙂

Ответ №1:

Как указывает StoryTeller, первый код ДЕЙСТВИТЕЛЬНО компилируется и связывается с C 17 и GCC 7.1 . Чтобы заставить это скомпилироваться с C 11 и более старыми версиями GCC, вам необходимо внеклассовое объявление массива:

 #include <array>
#include <iostream>

struct Bar {

    using Option = size_t;
    using Options = std::array<Option, 0>;
    static constexpr Option default_option = 8080;
    static constexpr Options default_options{};

    //template <typename OptionT = Options>
    Bar(
        Options options = default_options,
        Option  option  = default_option  
       ){
        std::cout << "Constructed with option " << option << std::endl;
        std::cout << "Constructed with options..." << std::endl;
        for (auto amp; other_option : options)
            std::cout << other_option << ", ";
        std::cout << std::endl;
    }
};

// !!!! Needed for C  11 and lower gcc<7.1 versions
constexpr Bar::Options Bar::default_options;

int main() {
    Bar bar;
}
  

Комментарии:

1. До C 17 строка constexpr Bar::Options Bar::default_options; фактически была определением , а не объявлением . Компилятору сообщается: «Я сказал, что у Bar него есть член default_options ; ему место здесь». Это не просто педантизм; это означает, что строка не может входить в файл заголовка, который включен в несколько расположений.

2. Меня немного смущает это утверждение, потому что в C 11, если у меня нет инициализации в классе: default_options{}; , то есть если я удаляю завершающие фигурные скобки, я получаю ошибку 'default_options' must have an initializer .

3. Возможно, более длинный ответ был бы уместен. Кажется, здесь есть тонкость, которую я упускаю.

4. Я не могу сейчас сделать более длинную запись, и я не могу найти ничего особенно хорошего и не перегруженного жаргоном, на что можно было бы ссылаться. Суть в том, что constexpr элементы данных работают больше как другие статические элементы данных, которые должны быть объявлены в классе и определены вне класса. В некоторых случаях объявление может иметь инициализацию (и должно иметь для constexpr , чтобы компилятор мог фактически использовать его как постоянное выражение), но вам все равно нужно внеклассовое определение.