#c #templates #template-matching #pointer-to-member
Вопрос:
Учитывая этот код
struct data {
int velocity;
};
template <typename Data>
class Collector {
// ...
public:
void add(const Dataamp; data) {}
template <typename T>
T average1(T Data::*field) const {
return T{}; // Some calculation here
}
template <T Data::*field>
T average2() const {
return T{}; // Some calculation here
}
};
void foo() {
Collector<data> collector;
// I have no problem handling the average by sending member as parameter
auto ok = collector.average1(amp;data::velocity);
// But compilation here fails
auto error = collector.average2<amp;data::velocity>();
}
Мое намерение состоит в том, чтобы заменить передачу указателей на элементы функциями с помощью аргументов шаблона, но не удалось одновременно сопоставить тип элемента и элемент, я могу сделать что-то вроде
template <typename T, T Data::*field>
T average2() const {
return T{}; // Some calculation here
}
но тогда я должен вызывать как
auto error = collector.average2<int, amp;data::velocity>();
и это, но уродливо и кажется ненужным
У вас есть идея о том, как это исправить, или лучший подход для сбора такого рода данных?
Заранее спасибо
Ответ №1:
В C 17 вы можете заставить шаблонную версию работать, расширив значение параметра шаблона до auto
и разрешив T
его позже, decltype()
например, с помощью a .
#include <type_traits>
template <typename Data>
class Collector {
// ...
public:
template <auto field>
auto average2() const {
using T = decltype(std::declval<Data>().*field);
return T{}; // Some calculation here
}
};
void foo() {
Collector<data> collector;
// Works perfectly fine
auto error = collector.average2<amp;data::velocity>();
}
В C 20 вы можете сделать это еще более чистым, ограничив field
Data
указатели на элементы. Это даст вам более жесткое разрешение перегрузки, а также более приятные сообщения об ошибках.
#include <type_traits>
template<typename PtrT, typename ObjT>
concept MemberObjectPointerFor = std::is_member_object_pointer_v<PtrT> amp;amp;
requires(PtrT ptr, ObjTamp; obj) {
{ obj.*ptr };
};
template <typename Data>
class Collector {
// ...
public:
template <MemberObjectPointerFor<Data> auto field>
auto average2() const {
using T = decltype(std::declval<Data>().*field);
return T{}; // Some calculation here
}
};
Комментарии:
1. Все, что было сказано, я бы лично просто придерживался
average1()
. Это то, что компиляторы могут тривиально оптимизировать большую часть времени.2. Я думаю, что использование параметра шаблона вместо функции-заполнителя arg более осмысленно и интуитивно понятно при чтении кода. Однако он сильно зависит от c 20, поэтому наличие такого подхода является ценным. OP не помечал версию c , поэтому она является законной, но в настоящее время все еще ограничивает. К этому моменту. Это очень сложная задача. Хотя это отлично работает в clang (trunk), gcc (trunk) в настоящее время выдает внутреннюю ошибку при этом подходе: godbolt.org/z/jn8n3Y9qr [дата выпуска: 2021/10/29] Известная проблема — или я должен подать?
3. @GlennTeitelbaum Концепция не является строго необходимой для того, чтобы это работало, она только немного очищает ситуацию. Базовая
auto
версия — C 17, которая на самом деле больше не является передовой. Ошибка gcc, безусловно, вызывает сожаление, хотя