перегрузка функции шаблона в не шаблонном классе с использованием SFINAE - PullRequest
0 голосов
/ 01 июля 2018

TL; DR

Пара шаблонизированных и перегруженных не-шаблонизированных функций-членов в не-шаблонизированном классе должна в конечном итоге перенаправиться через одну и ту же функцию-член для выполнения фактической работы. Все перегрузки и шаблоны выполняются для преобразования «буфера данных» в тип gsl::span<std::byte> (по сути, близкий по отношению к std::array<std::byte, N> из библиотеки поддержки Guidelines )


Стена кода

#include <array>
#include <cstdlib>
#include <iostream>
#pragma warning(push)
#pragma warning(disable: 4996)
#include <gsl.h>
#pragma warning(pop)

// convert PoD into "memory buffer" for physical I/O
// ignore endianness and padding/alignments for this example
template<class T> gsl::span<std::byte> byte_span(T& _x) {
    return { reinterpret_cast<std::byte*>(&_x), sizeof(T) };
}

// implementation of physical I/O (not a functor, but tempting)
struct A {
    enum class E1 : uint8_t { v1 = 10, v2, v3, v4 };
    bool f(uint8_t _i1, gsl::span<std::byte> _buf = {});    // a - "in the end" they all call here
    bool f(E1 _i1, gsl::span<std::byte> _buf = {});         // b
    template<typename T, typename = std::enable_if_t< std::is_integral<T>::value > >
        bool f(uint8_t _i1, T& _val);                       // c
    template<typename T, typename = std::enable_if_t< std::is_integral<T>::value > >
        bool f(E1 _i1, T& _val);                            // d
};

bool A::f(uint8_t _i1, gsl::span<std::byte> _buf)
{
    std::cout << "f() uint8_t{" << (int)_i1 << "} with " << _buf.size() << " elements\n";
    return true;
}

bool A::f(E1 _i1, gsl::span<std::byte> _buf)
{
    std::cout << "f() E1{" << (int)_i1 << "} with " << _buf.size() << " elements\n\t";
    return f((uint8_t)_i1, _buf);
}

template<class T, typename>
bool A::f(uint8_t _i1, T& _val)
{
    std::cout << "template uint8_t\n\t";
    return f(_i1, byte_span(_val));
}

template<class T, typename>
bool A::f(E1 _i1, T& _val)
{
    std::cout << "template E1\n\t";
    return f(_i1, byte_span(_val));
}

int main(){
    A a = {};
    std::array<std::byte, 1> buf;
    long i = 2;

    // regular function overloads
    a.f(1, buf);           // should call (a)
    a.f(A::E1::v1, buf);   // should call (b)

    // template overloads
    a.f(2, i);             // should call (c)
    a.f(A::E1::v2, i);     // should call (d)

    struct S { short i; };
    // issue
    //S s;
    //a.f(3, s);             // should call (c)
    //a.f(A::E1::v3, s);     // should call (d)

    //// bonus - should use non-template overrides
    //S sv[2] = {};
    //a.f(5, sv);            // should call (a)
    //a.f(A::E1::v1, sv);    // should call (b)
}

Подробнее

struct S - это PoD, и соблазнительно изменить enable_if шаблона для использования std::is_trivial или std::is_standard_layout. К сожалению, оба этих решения "слишком много" и в итоге сопоставляют std::array (даже если они do исправляют ошибку компиляции блока //issue).

Решение, которое у меня есть сейчас, выглядит тупиковым, так как мое интуитивное чувство состоит в том, чтобы начать добавлять дополнительные параметры шаблона, и, похоже, очень скоро оно станет очень волосатым: (

Вопрос

Моя цель - добиться следующего: использовать функцию-член class A bool f() без слишком больших синтаксических издержек для любого PoD (возможно, включая массивы C - см. «Бонус» в коде), как показано в теле main() и нет затрат на вызов функции времени выполнения для типов, которые автоматически преобразуются в gsl::span (например, std::array и std::vector).

В идеале ...

Мне бы хотелось иметь одну шаблонизированную функцию для каждого первого параметра (E1 или uint8_t) с несколькими специализациями, перечисленными вне тела класса, чтобы еще больше уменьшить воспринимаемый беспорядок кода в объявлении класса, и я не могу выяснить способ, как правильно сделать это. Что-то вроде следующего (недопустимый код C ++ ниже!):

struct A {
// ...
template<typename T> bool f(uint8_t _i1, T& _val);
template<typename T> bool f(E1 _i1, T& _val);
};

template<> bool f<is_PoD<T> && not_like_gsl_span<T>>(uint8_t /*...*/}
template<> bool f<is_PoD<T> && not_like_gsl_span<T>>(E1 /*...*/}
template<> bool f<is_like_gsl_span<T>>(uint8_t /*...*/}
template<> bool f<is_like_gsl_span<T>>(E1 /*...*/}

Если это недостижимо, я бы хотел знать почему.

Я нахожусь на MSVC 2017 с включенным C ++ 17.

1 Ответ

0 голосов
/ 03 июля 2018

Первый ответ был немного неправильным, по некоторым причинам я был полностью смущен gsl. Я думал, что это что-то супер конкретное. Я не пользуюсь библиотекой поддержки директив, хотя я ее видел и выглядит хорошо. Исправлен код для правильной работы с gsl::span типом.

struct A {
  enum class E1 : uint8_t { v1 = 10, v2, v3, v4 };

private:
  template <typename T>
  static auto make_span(T& _x) ->
    typename std::enable_if<std::is_convertible<T&, gsl::span<std::byte>>::value,
                            gsl::span<std::byte>>::type {
    std::cout << "conversion" << std::endl;
    return _x;
  }

  template <typename T>
  static auto make_span(T& _x) ->
    typename std::enable_if<!std::is_convertible<T&, gsl::span<std::byte>>::value,
                            gsl::span<std::byte>>::type {
    std::cout << "cast" << std::endl;
    return {reinterpret_cast<std::byte*>(&_x), sizeof(T)};
  }

public:
  template <typename T, typename U>
  bool f(T _i, U& _buf) {
    static_assert(
      std::is_convertible<U&, gsl::span<std::byte>>::value || std::is_trivial<U>::value,
      "The object must be either convertible to gsl::span<std::byte> or be of a trivial type");

    const auto i = static_cast<uint8_t>(_i);
    const auto span = make_span(_buf);
    std::cout << "f() uint8_t{" << (int)i << "} with " << span.size() << " elements\n";
    return true;
  }
};
...