2016-09-16 5 views
6

Это mcve моего кода: (если это имеет значение, Options_proxy и Options имеют constexpr ctors). Я знаю, что это еще далеко не из простых, но не может упростить его больше пока еще экспонирование ошибки:Неполный тип в функции друга

template <class Impl> 
struct Options_proxy : Impl { 
    using Flag = typename Impl::Flag; 

    friend constexpr auto operator!(Flag f) -> Options_proxy { 
    return {}; // <-- error here 
    }; 
}; 

template <class Impl> 
struct Options : Impl { 
    using Flag = typename Impl::Flag; 
}; 

struct File_options_impl { 
    enum class Flag : unsigned { nullflag, read, write }; 

    friend constexpr auto operator!(Flag f) -> Options_proxy<File_options_impl>; 
}; 

using File_options = Options<File_options_impl>; 

auto foo() 
{ 
    !File_options::Flag::write; // <-- required from here 
} 

НКУ 6 и 7 дают эту ошибку:

In instantiation of 'constexpr Options_proxy<File_options_impl> operator!(Options_proxy<File_options_impl>::Flag)': 
required from ... etc etc... 
error: return type 'struct Options_proxy<File_options_impl>' is incomplete 

лязг компилироваться OK.

код соответствует в НКУ, если:

  • я удалить constexpr из operator!

или

  • добавить объект типа Options_proxy<File_options_impl> перед вызовом оператора:

так:

auto foo() 
{ 
    Options_proxy<File_options_impl> o; 
    !File_options::Flag::write; // <-- now OK in gcc also 
} 

Является ли это ошибка или НКИ некоторое Неопределенное поведение в коде, что-то вроде неопределенного или нет диагностики требуется?


Что касается мотивации написания такого кода:

Я хочу создать (для развлечения в основном) типа безопасной системы флагов/опций (без макросов):

Библиотеки черной магия:

template <class Impl> 
    requires Options_impl<Impl> 
struct Options : Impl { 
    // go crazy 
}; 

код пользователя:

struct File_options_impl { 
    // create a system where here the code 
    // needs to be as minimal as possible to avoid repetition and user errors 

    // this is what the user actually cares about 
    enum class Flag : unsigned { nullflag = 0, read = 1, write = 2, create = 4}; 

    // would like not to need to write this, 
    // but can't find a way around it 
    friend constexpr auto operator!(Flag f1) -> Options_proxy<File_options_impl>; 
    friend constexpr auto operator+(Flag f1, Flag f2) -> Options_proxy<File_options_impl>; 
    friend constexpr auto operator+(Flag f1, Options_proxy<File_options_impl> f2) -> Options_proxy<File_options_impl>; 
}; 

using File_options = Options<File_options_impl>; 

, а затем:

auto open(File_options opts); 

using F_opt = File_options::Flag; 
open(F_opt::write + !F_opt::create); 

ответ

1

Похоже, что это ошибка gcc.Вот MCVE (4 линии):

struct X; 
template<int> struct A { friend constexpr A f(X*) { return {}; } }; 
// In instantiation of 'constexpr A<0> f(X*)': 
// error: return type 'struct A<0>' is incomplete 
struct X { friend constexpr A<0> f(X*); }; 
auto&& a = f((X*)0); 

Это принято clang и MSVC.

Как вы заметили, GCC принимает ту же программу без constexpr, или если явно экземпляр A<0> (например, с template struct A<0>;) перед auto&& a = f((X*)0);. Это говорит о том, что проблема НКУ испытывает в шаблоне класса неявной конкретизации [temp.inst]:

1 - Unless a class template specialization has been explicitly instantiated (14.7.2) or explicitly specialized (14.7.3), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program.

Шаблон класса A<0> требуется в return заявлении constexpr A<0> f(X*), так должно быть неявно инициализируется в этой точке , Хотя определение функции друга лексически входит в класс A, класс не должен считаться неполным в определении функции друга; например следующая программа нешаблонной общепризнанна:

struct Y; 
struct B { friend constexpr B f(Y*) { return {}; } }; 
struct Y { friend constexpr B f(Y*); }; 
auto&& b = f((Y*)0); 

Интересно, как GCC и лязга (хотя и не MSVC) имеет проблемы со следующей программой (опять же, фиксированной путем удаления constexpr или явно инстанцирования с template struct C<0>;):

struct Z; 
template<int> struct C { friend constexpr C* f(Z*) { return 0; } }; 
struct Z { friend constexpr C<0>* f(Z*); }; 
// error: inline function 'constexpr C<0>* f(Z*)' used but never defined 
auto&& c = f((Z*)0); 

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

template class Options_proxy<File_options_impl>; 
0

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

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

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

Учитывая, что флаги уникальны, вы можете попытаться переместить оператор за пределы Options_proxy.

template <class Impl> 
struct Options_proxy : Impl { 
    using Flag = typename Impl::Flag; 
}; 

template <class Impl, typename Flag = Impl::Flag> 
constexpr Options_proxy<Impl> operator!(Flag f) { 
    return {}; 
} 

template <class Impl> 
struct Options : Impl { 
    using Flag = typename Impl::Flag; 
}; 

struct File_options_impl { 
    enum class Flag : unsigned { nullflag, read, write }; 

    friend constexpr Options_proxy<File_options_impl> operator!(Flag f); 
    // Or if that doesn't work: 
    friend constexpr Options_proxy<File_options_impl> operator!<File_options_impl>(Flag f); 
}; 
+0

Благодарим вас за ответ. 1. У отдельной специализации будут разные члены 'Flag'. Они могут иметь тот же самый флаг, если 'Impl :: Flag' является псевдонимом. Это не произойдет в текущей системе. 2. Как я уже сказал, это mcve, поэтому реальный код намного сложнее. Я согласен, что это немного злоупотребляет объявлением друга, но я не вижу другого способа определить операторы для 'Flag' вне' File_options_impl' (в библиотечной части кода). 3. Я не уверен, что это невозможно. Я был бы очень разочарован, если бы это было невозможно. – bolov

Смежные вопросы