Сопоставление указателя на элементы и его типа в качестве параметров шаблона

#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, безусловно, вызывает сожаление, хотя