c++ SFINAE +模板专门化

iq0todco  于 2023-01-03  发布在  其他
关注(0)|答案(2)|浏览(168)

我需要一个图像类模板,这将与不同数量的维度和不同格式的图像。我还想为1D,2D和3D图像定义不同的类模板专业化(只有这三个,其他人将离开未定义-所以他们不能使用)。
摘要代码:

enum image_format {R, G, B, RG, GB, RB, RGB}; // <--- Image format enumeration.
template<int dimensions, image_format format> // <--- Primary image class template.
class image;                                  // Only few dimensions will be defined.

template<image_format format> // <--- Partial template specialization used here
class image<2, format>        // to define image class for 2D images only.
{
private:
    /* Class data here */
public:
    image(int size_x, int size_y, char * bytes); // <--- 2D image constructor initializes
                                                 // object with given size and data.
};

template<image_format format, typename = typename std::enable_if // <--- SFINAE used here with unnamed template
        <format == R or format == G or format == B>::type>       // argument and accepts only R or G or B.
image<2, format>::image(int size_x, int size_y, char * bytes)
{
    /* Code for R- or G- or B-formatted images only */
}
template<image_format format, typename = typename std::enable_if
        <format == RG or format == GB or format == RB>::type>
image<2, format>::image(int size_x, int size_y, char * bytes)
{
    /* Code for RG- or GB- or RB-formatted images only */
}
template<image_format format, typename = typename std::enable_if
        <format == RGB>::type>
image<2, format>::image(int size_x, int size_y, char * bytes)
{
    /* Code for RGB-formatted images only */
}

我可以在构造函数上使用模板专用化来为每种格式使用不同的代码,但是构造函数需要接受多个模板参数。(即image<2, R>image<2, G>image<2, B>),第二个包含两个字母格式的图像,以此类推。如果要分离R、G和B构造函数,我需要在每个模板专用化中编写相同的代码,所以我想知道我是否可以使用SFINAE或这里的任何东西只写一次代码。
当我试图编译这段代码时,我得到了两个redefinition of image<2, format>::image(我猜它来自多个构造函数实现)和/或too many template parameters in template redeclaration(它来自构造函数实现模板中的额外参数,用于SFINAE的参数)。看起来我离实现我想要的不远了,但仍然有一些错误,我不知道是什么。

  • 我知道 * static_assert * 或模板参数检查在这里就足够了,但是我想知道如果没有这些检查,只使用模板专门化和SFINAE是否可以做我想做的事情。*
irtuqstp

irtuqstp1#

这比您想象的要简单一些...但首先,您使用std::enable_if是错误的:如果你没有提供类型,那么它默认为void,意味着 * 你所有的方法解析为相同的类型 *。打开你的编译器的警告,它会告诉你这类事情。
下面是一种完成测试程序的方法:

// cl /EHsc /W4 /Ox /std:c++17 /wd4100 a.cpp
// clang++ -Wall -Wextra -Werror -pedantic-errors -O3 -std=c++17 -Wno-unused-parameter a.cpp

#include <iostream>
#include <type_traits>

enum image_format {R, G, B, RG, GB, RB, RGB}; // <--- Image format enumeration.

template<int Dimensions, image_format Format>
struct image
{
  image(int size_x, int size_y, char * bytes);

private:
  // Here is the common code for the image<2,R|G|B> constructor to use.
  void image2R(int size_x, int size_y, char * bytes)
  {
    std::cout << "R or G or B\n";
  }

  // (Repeat for image<2,RG|RB|GB>)
  // void image2RG(int size_x, int size_y, char * bytes)
  // {
  //   ...
  // }
};

// Here our constructors will simply dispatch to the common code
template<> image<2, R>::image(int size_x, int size_y, char * bytes) { image2R(size_x, size_y, bytes); }
template<> image<2, G>::image(int size_x, int size_y, char * bytes) { image2R(size_x, size_y, bytes); }
template<> image<2, B>::image(int size_x, int size_y, char * bytes) { image2R(size_x, size_y, bytes); }
// template <> void image<2, RG>::image2RG(int, int, char *) = delete;
// ...

// (Repeat for image<2, RG>, etc.)

// The RGB method only has one variant, so no need for a common code method.
template<> image<2, RGB>::image(int size_x, int size_y, char * bytes)
{
  std::cout << "RGB\n";
}
template<> void image<2, RGB>::image2R(int, int, char *) = delete;
// template<> void image<2, RGB>::image2R(int, int, char *) = delete;
// ...

int main()
{
  image<2, RGB> imgRGB(10, 10, nullptr);
  image<2, G>   imgG  (54, -7, nullptr);
  
//  image<3, RGB> fooey (11, 11, nullptr);  // link error: unresolved external symbol
//  image<2, RG>  quuxl (17, 17, nullptr);  // link error: unresolved external symbol

//  imgRGB.image2R(9,8,nullptr);  // attempting to use a deleted function
//  (If you do uncomment the above line, make sure to
//   comment out the "private:" in the class definition above)

  std::cout << "Hello world!\n";
}

这是因为:

  • 我们不会为不需要的模板组合定义构造函数,因此您的用户无法示例化它们。
  • 我们将公共构造代码放入一个私有的“helper”函数中,并从适当的构造函数分派给它。为每个有效的公共性编写一个构造函数helper。
  • 我们= delete不属于给定模板组合的公共帮助函数,这样它们就不是该类的一部分。

这不是实现目标的唯一方法。还有其他方法。就我个人而言,我不确定你的类设计是我能接受的......但我不知道你的需求,也不知道你对它们有多大的控制力。
对于其他阅读,您可能想在谷歌上搜索“SFINAE”和“C++标记调度”。

pgky5nke

pgky5nke2#

不能仅在定义中使用SFINAE。声明签名和定义签名应匹配。由于默认参数不是签名的一部分,请使用

template <typename T, typename = std::enable_if_t<cond<T>>>

使用

template <typename T, std::enable_if_t<cond<T>, int> = 0>

在C++17中,if constexpr可能会有所帮助:

template<image_format format> // <--- Partial template specialization used here
class image<2, format>        // to define image class for 2D images only.
{
private:
    /* Class data here */
public:
    image(int size_x, int size_y, char * bytes)
    {
        if constexpr (format == image_format::R
                    || format == image_format::G
                    || format == image_format::B) {
            // ...
        } else if constexpr (format == image_format::RG) {
            // ...
        } // ...
    }
};

C++20中SFINAE的替代方法是使用requires进行约束:

template<image_format format> // <--- Partial template specialization used here
class image<2, format>        // to define image class for 2D images only.
                              // Note that requires might be also used to avoid specialization
{
private:
    /* Class data here */
public:
    image(int size_x, int size_y, char* bytes)
    requires(format == image_format::R
          || format == image_format::G
          || format == image_format::B)
    {
        // ...
    }
    image(int size_x, int size_y, char* bytes)
    requires(format == image_format::RG
          || format == image_format::GB
          || format == image_format::RB)
    {
        // ...
    }

    // ...
}

相关问题