Использование объявления переменных аргументов. Как выбрать правильную функцию?

У меня есть дизайн, основанный на политике, где у меня есть некоторая функция, foo() определенная в одних политиках, но не в других. Базовый класс всегда наследуется от некоторого класса с именем AlwaysBase, который имеет подчиненную функцию foo().

Я хочу сделать так, чтобы, если существует лучший foo() (один из класса Policy), он всегда выбирался.

Вот код:

// logic for determining if a class has a member function (not really related)
template <class> struct sfinae_true: std::true_type{};
template <class T> static auto test_foo(int) -> sfinae_true<decltype(std::declval<T>().foo())>;
template <class> static auto test_foo(long) -> std::false_type;
template <class T> struct has_foo : decltype(test_foo<T>(0)){};

class AlwaysBase{ public: void foo(){ cout << "in AlwaysBase foo()" << endl; } };
class BPolicy{};
class CPolicy{ public: void foo(){ cout << "in Cpolicy foo()" << endl; } };

template<typename T, typename ...Args>
class A: public T, public A<Args...>
{
public:
    using std::conditional_t<has_foo<T>::value, T, AlwaysBase>::foo;
};

template<typename T>
class A<T>: public T, public AlwaysBase
{
public:
    using std::conditional_t<has_foo<T>::value, T, AlwaysBase>::foo;
};

int main()
{
    A<BPolicy> b;
    b.foo();     // outputs: In AlwaysBase foo()

    A<CPolicy> c;
    c.foo();     // outputs: In CPolicy foo()
    
    A<CPolicy, BPolicy> cb;
    cb.foo();    // outputs: In CPolicy foo()

    A<BPolicy, CPolicy> bc;
    bc.foo();    // outputs: In AlwaysBase foo() // I WANT THIS TO OUTPUT!!!!: In CPolicy foo
}

Я понимаю, что здесь происходит, когда BPolicy является первым, этот оператор using сначала используется в классе с переменными аргументами и затеняет любой предыдущий оператор using, который можно было бы найти в завершающем базовом классе (а именно CPolicy::foo).

Я хотел бы, чтобы порядок не имел значения при указании политик, и если у политики есть определитель, его всегда следует выбирать первым, а не AlwaysBase::foo.


person Tyler Kelly    schedule 08.07.2020    source источник
comment
Пожалуйста, удалите совершенно ненужное ключевое слово inline... похоже, вы действительно не знали, что оно делает :-)   -  person Klaus    schedule 08.07.2020
comment
Что вы хотите, чтобы произошло, если несколько политик имеют foo?   -  person cigien    schedule 08.07.2020
comment
@Klaus скопируйте и вставьте ошибку из моей довольно сложной функции :)   -  person Tyler Kelly    schedule 08.07.2020
comment
@cigien, тогда порядок будет преобладать, но в моем приложении не имеет смысла, чтобы те политики, которые содержат foo, были там   -  person Tyler Kelly    schedule 08.07.2020
comment
Я не понимаю, почему вы делаете условный подход в этом случае. Просто сделайте базовый класс пустым foo() и переопределите его в производном классе. Объявление использования не требуется, как в классическом шаблоне CRTP.   -  person SergeyA    schedule 08.07.2020
comment
Для меня ничего из кода не имеет никакого смысла! Поскольку самый верхний класс предоставит функцию foo(), она будет взята, если нет, будет взята функция foo глубже в иерархии. Как у вас, у AlwaysBase есть foo, который работает без каких-либо вещей SFINAE!   -  person Klaus    schedule 08.07.2020


Ответы (1)


Используйте рекурсию. Если разрешены пустые списки, это очень просто:

// recursive invariant: As<Ts...>::foo is always the foo from the first capable Ts or from AlwaysBase
template<typename... Ts>
class A : public AlwaysBase { // template<> class A<>
public:
    // there are no capable Ts
    using AlwaysBase::foo;
};
template<typename T, typename... Ts>
class A<T, Ts...> : public T, public A<Ts...> {
public:
    // from the A<Ts...> superclass, not AlwaysBase
    // try to use T, fall back to Ts, and only then fall back to AlwaysBase
    using std::conditional_t<has_foo<T>::value, T, A<Ts...>>::foo;
};

Ограничение непустыми списками немного раздражает:

template<typename T, typename... Ts>
class A : public T, public A<Ts...> {
public:
    using std::conditional_t<has_foo<T>::value, T, A<Ts...>>::foo;
};
template<typename T>
class A<T> : public T, public AlwaysBase {
public:
    using std::conditional_t<has_foo<T>::value, T, AlwaysBase>::foo;
};

Однако порядок по-прежнему имеет значение. Если две политики обеспечивают foo, выигрывает первая.

person HTNW    schedule 08.07.2020
comment
Не могу уловить вашу точку зрения. Код OP уже использует рекурсию, не так ли? Кстати: ваше условие не имеет смысла, так как компилятор уже выбрал самую верхнюю реализацию foo(). - person Klaus; 08.07.2020
comment
@HTNW именно то, что я искал, и это имеет большой смысл! Спасибо за помощь! - person Tyler Kelly; 08.07.2020
comment
@Клаус Да, но и нет. Ts включаются как суперклассы рекурсивно, а выбор foo - нет. Логика OP говорит: если первый из Ts имеет foo, используйте его, иначе AlwaysBase::foo, что не является рекурсивным. Я говорю: если у первого из Ts есть foo, используйте его, в противном случае ищите рекурсивно в хвосте, используя AlwaysBase::foo, если это не удается, что есть. Кроме того, удаление условных выражений и разрешение компилятору самому выбирать вызывает двусмысленность. Может быть, я просто что-то упускаю из виду. - person HTNW; 08.07.2020
comment
и если политика говорит, что не первая политика. Может быть, я неправильно прочитал :-) Мое понимание вопроса было таким: если один из .... использовал его вместо стандартного. В любом случае: если вы хотите проверить только первое, вам определенно не нужна рекурсия, вы просто выбираете первое или значение по умолчанию. И здесь нет двусмысленности, так как без предложения using самое верхнее объявление скроет все остальные, даже если есть неоднозначные перегрузки. - person Klaus; 08.07.2020
comment
@HTNW с первым упомянутым вами подходом, возможно ли включить аргументы шаблона по умолчанию? - person Tyler Kelly; 21.07.2020