【问题标题】:template function overload in non-template class using SFINAE使用 SFINAE 的非模板类中的模板函数重载
【发布时间】:2018-07-01 19:27:38
【问题描述】:

TL;DR

非模板化类中的几个模板化和重载的非模板化成员函数最终都应该通过同一个成员函数进行路由以执行实际工作。所有重载和模板化都是为了将​​“数据缓冲区”转换为gsl::span<std::byte> 类型(本质上与Guidelines Support Library 中的std::array<std::byte, N> 密切相关)


代码墙

#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_trivialstd::is_standard_layout。不幸的是,这两种解决方案都“抢占太多”并最终匹配 std::array(即使它们确实修复了 //issue 块的编译错误)。

我现在的解决方案看起来像是一个死胡同,因为我的直觉是开始添加更多模板参数,而且它似乎很快就会变得非常棘手:(

问题

我的目标是实现以下目标:使用 class Abool f() 成员函数,而不会对任何 PoD(可能包括 C 数组 - 参见代码中的“奖励”)产生过多的语法开销,如正文所示main() 并且对于可自动转换为 gsl::span 的类型(如 std::arraystd::vector)没有运行时函数调用开销。

理想情况下...

我希望每个第一个参数(E1uint8_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 /*...*/}

如果这无法实现,我想知道原因。

我正在使用启用 C++17 的 MSVC 2017。

【问题讨论】:

  • 您确定它应该在a.f(1, buf)a.f(A::E1::v1, buf) 的情况下调用(a) 吗?更像是应该调用 (c),但不会因为它被 enable_if 条件忽略。
  • 标准规定,如果找到合适的匹配,则选择“普通重载”函数而不是模板
  • 确实如此,但在这些情况下,您传递了std::array,而使用gsl::span 声明的方法。所以显然类型是不同的。如果你通过了gsl::span,那就很清楚了。你为什么期望它调用 (a) 和 (b)?
  • 我的问题是“如何让它调用 a) 而不是 b)”。好吧,无论如何,这是问题的一部分

标签: c++ c++11 sfinae guideline-support-library


【解决方案1】:

第一个答案有点错误,由于某些原因,我完全被 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;
  }
};

【讨论】:

  • 这是“足够接近”。不完全是一个解决方案(所以没有这样标记),但绝对是我所拥有的改进。
  • 您还可以检查f中的T类型是E1还是static_assert中的uint8_t
猜你喜欢
  • 1970-01-01
  • 2018-08-16
  • 1970-01-01
  • 2016-05-23
  • 2010-12-20
  • 1970-01-01
  • 2019-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多