c #function #templates #lambda #member
#c #функция #шаблоны #лямбда #Участник
Вопрос:
приведенный ниже код демонстрирует интересное поведение:
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> amp;x)const;
template<class F> void foo(F f);
};
template<typename T> void B<T>::foo(B<T> amp;x)const{cout<<"foo_B"<<endl;}
template<typename T> template<typename F> void B<T>::foo(F f){cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
мой ожидаемый результат
foo_B
foo_F
но фактический результат
foo_F
foo_F
это зависит от void foo(B<T> amp;x)
того, объявлено ли const
. Если const
опущено, результат соответствует ожидаемому.
Кроме того, if const
добавляется к void foo(F f)
выводу, как и ожидалось.
Однако, void foo(B<T> amp;x)
не изменит this
, тогда void foo(F f)
как изменится this
. Таким образом, требуется текущий макет.
Любая идея, как решить эту проблему без удаления const
, очень ценится.
Ответ №1:
Проблема здесь в том, что, поскольку void foo(B<T> amp;x)const;
имеет значение const, оно должно было бы определять const для объекта, для которого вы вызываете функцию. Это не так точно, как template<class F> void foo(F f);
обеспечивает соответствие, поскольку ему не нужно выполнять эту квалификацию const. Вот почему он используется для обоих вызовов.
Вы можете исправить это, также указав const для версии шаблона, например:
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> amp;x)const;
template<class F> void foo(F f)const;
};
template<typename T> void B<T>::foo(B<T> amp;x)const{cout<<"foo_B"<<endl;}
template<typename T> template<typename F> void B<T>::foo(F f)const{cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
Который будет печатать
foo_B
foo_F
Другим вариантом было бы использовать SFINAE, чтобы ограничить версию шаблона от исключения B<T>
‘s. Это будет выглядеть так
#include <iostream>
using namespace std;
template<class T>
class B{
public:
void foo(B<T> amp;x)const;
template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool> = true>
void foo(F f);
};
template<typename T> void B<T>::foo(B<T> amp;x)const{cout<<"foo_B"<<endl;}
template<typename T> template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool>>
void B<T>::foo(F f){cout<<"foo_F"<<endl;}
int main(){
B<int> a;
B<int> b;
b.foo(a);
b.foo([](){;});
return(0);
}
и имеет тот же результат, что и в первом примере.
Комментарии:
1. Привет. Спасибо за ответ. но этот подход не решит проблему, поскольку
foo(F f)
должен иметь возможность изменять объект, что было бы невозможно, если бы оно было объявленоconst
.2. @user1407220 Я обновил ответ версией, которая позволит это.
3. @user1407220 Просто чтобы подчеркнуть разницу между моим подходом, поскольку они тонкие: это вызовет
foo_F
перегрузку для аргумента другой специализацииB
, который может быть или не быть тем, что нужно OP. Например. изменение типаa
в последнем примере выше на, напримерB<double> a;
, вызоветfoo_F
перегрузку дляb.foo(a);
.4. @dfrib При значении true, если вы сделали то же самое, ваш код не будет компилироваться, поскольку
void foo(B<T> amp;x)
не является шаблоном функции. Вот почему я сделал это таким образом. Неясно, какого именно поведения хочет OP.5. @NathanOliver Да, верно.
Ответ №2:
Преобразование квалификации (здесь: для неявного параметра объекта) не является преобразованием идентификатора и имеет стоимость в рейтинге разрешения перегрузки
Функция void foo(B<T>amp; x) const
const
-член имеет значение — , тогда как функция-член шаблона template<class F> void foo(F f)
— нет. Это означает, что последнее лучше подходит для вызова, в котором неявный параметр объекта не является const, согласно [over.ics.rank]/3.2.5:
Стандартная последовательность преобразования S1 является лучшей последовательностью преобразования, чем стандартная последовательность преобразования S2, если
- […], или, если не так, S1 и S2 отличаются только своим квалификационным преобразованием ([conv.qual]) и дают аналогичные типы T1 и T2 соответственно, где T1 может быть преобразован в T2 с помощью квалификационного преобразования.
Если вы const
-квалифицируете автоматическую переменную b
в main, будет выбрана перегрузка без шаблона:
// ^^^ `foo` overloads as in OP's example
B<int> a{};
B<int> const b{}
b.foo(a); // foo_B
Если вы вместо const
этого измените качество функции-члена шаблона foo
, то не шаблонная функция будет совпадать с перегрузкой шаблона (требуется постоянная квалификация для неявного параметра объекта), и в этом случае не шаблонная функция выбирается как наилучшая жизнеспособная перегрузка, согласно [over.match.best]/2.4.
Если вы никогда не хотите, чтобы конкретная перегрузка участвовала в разрешении перегрузки для предиката типа: удалите ее
Однако b не может быть объявлен const в реальном приложении, что сводится к созданию копии, скажем, const c(b); а затем используйте c.foo(a)
Вы могли бы использовать признак для удаления функции-члена шаблона, если аргумент шаблона для его параметра типа template является специализацией шаблона B
класса:
#include <iostream>
#include <type_traits>
template <class T, template <class...> class Primary>
struct is_specialization_of : std::false_type {};
template <template <class...> class Primary, class... Args>
struct is_specialization_of<Primary<Args...>, Primary> : std::true_type {};
template <class T, template <class...> class Primary>
inline constexpr bool is_specialization_of_v{is_specialization_of<T, Primary>::value};
template <class T> class B {
public:
void foo(B<T> amp;x) const { std::cout << "foo_B" << std::endl; }
template <class F, typename = std::enable_if_t<!is_specialization_of_v<F, B>>>
void foo(F f) {
std::cout << "foo_F" << std::endl;
}
};
int main() {
B<int> a;
B<int> b;
b.foo(a);
b.foo([]() { ; });
return (0);
}
где мы использовали is_specialization_of
особенность P2098R1 (просто обратите внимание, что это имеет отклонения в реализации для аргументов шаблона, которые являются шаблонами псевдонимов — несколько недоопределенный IIRC).
Обратите внимание, что при таком подходе ни одна из перегрузок не будет жизнеспособной для аргумента, который является другой специализацией B
(чем у неявного параметра объекта).
Комментарии:
1. Привет. конечно, подход. Однако
b
не может быть объявленоconst
в реальном приложении, что сводится к созданию копии, скажемconst c(b);
, а затем использованиюc.foo(a)
. Поскольку обе функции могут существенно отличаться по своему телу, забывание последней (то есть использованиеb.foo(a)
вместо нее) является гарантией segfault . Лучшим решением была бы структура, в которой компилятор поднимал бы флаг.2. @user1407220 Я расширил с помощью SFINAE-подхода, чтобы удалить перегрузку шаблона, когда аргумент шаблона для него является специализацией
B
.
Ответ №3:
Довольно простой, даже если немного подробный способ — привести объект к const, когда вы хотите вызвать версию const:
static_cast<const B<int>>(b).foo(a);
Этого достаточно для отображения вызова foo_B
…