【问题标题】:Specialize member functions based on size of member container根据成员容器的大小专门化成员函数
【发布时间】:2019-02-15 09:01:33
【问题描述】:

我有一个包含一些静态大小容器的类:

template <typename Container>
struct Point {
    Container container;
    ... 
    void bar();
}

Container 类可能如下所示:

struct Container1 {
    static constexpr size_t size = 5;
}

现在我想根据容器的大小专门化bar 方法。我不明白该怎么做。

编辑:

我想要一个 C++11 解决方案。 C++14 可能有效,但我们使用的编译器通常对 C++14 的支持参差不齐。

编辑:

Stack Danny 提出了一个使用 Clang 编译但不使用 GCC 编译的解决方案。

【问题讨论】:

  • 自 C++17 起,if constexpr(如果签名不应更改。OT:bar(); 不是有效的函数声明,contexpr 不是关键字。
  • 对不起。我忘了提到 C++11
  • 令我惊讶的是,这只是在 C++17 中引入的。

标签: c++ c++11 templates template-specialization


【解决方案1】:

使用 SFINAE 代替专业化

template <typename Container>
class Point {
    Container container;
    template<size_t S> std::enable_if_t<S==3>
    bar_t() { std::cout << "specialisation for size=3\n"; }
    template<size_t S> std::enable_if_t<S==5>
    bar_t() { std::cout << "specialisation for size=5\n"; }
    template<size_t S> std::enable_if_t<S==42>
    bar_t() { std::cout << "specialisation for size=42\n"; }
  public:
    void bar()
    { bar_t<Container::size>(); }
};

std::enable_if_t 是 C++14,但你可以自己简单地声明它:

#if __cplusplus < 201402L
template<bool C, typename T=void>
using enable_if_t = typename enable_if<C,T>::type;
#endif

顺便说一句,你的问题闻起来像 XY problem:你真的需要为 Container::size 专门化 bar() 吗?在下面的例子中,一个循环被展开为任意大小N

template<typename scalar, size_t N>
class point // a point in R^N
{
    scalar array[N];
  public:
    // multiplication with scalar
    point& operator*=(scalar x) noexcept
    {
        // unroll loop using template meta programming
        loop([array,x](size_t i) { array[i] *= x; };);
        /* alternatively: rely on the compiler to do it
        for(size_t i=0; i!=N; ++i)
            array[i] *= x;
        */
        return *this;   
    }
  private:
    template<size_t I=0, typename Lambda>
    static enable_if_t<(I<N)> loop(Lambda &&lambda)            
    {
        lambda(I);
        loop<I+1>(lambda);
    }
    template<size_t I=0, typename Lambda>
    static enable_if_t<(I>=N)> loop(Lambda &&)         {}
};

【讨论】:

  • 出于好奇:除了对所有补码进行 AND 运算之外,还有更简单的方法来设置默认值吗?
【解决方案2】:

称为模板特化,工作原理如下:

#include <cstdint>
#include <iostream>
    
struct Container1 {
    static constexpr size_t size = 5;
};
struct Container2 {
    static constexpr size_t size = 3;
};
    
    
template <typename Container>
struct Point {
    Container container;
    
    void bar() {
        this->bar_templated<Container::size>();
    }
    
private:
    template<std::size_t size>
    void bar_templated() {
        std::cout << "default\n";
    }
    template<>
    void bar_templated<3>() {
        std::cout << "specialized <3>\n";
    }
    template<>
    void bar_templated<5>() {
        std::cout << "specialized <5>\n";
    }
};
    
int main(){
    Point<Container1> p1;
    p1.bar();
    Point<Container2> p2;
    p2.bar();
}

输出

specialized <5>
specialized <3>

由于 gcc 中的bug 85282 使其成为impossible to compile an explicit specialization in non-namespace scope(感谢@songyuanyao),出现错误:

25:14:错误:非命名空间范围“结构点”中的显式特化

26:27:错误:主模板声明中的模板 ID 'bar_templated'

...

30:10: 错误:'void Point::bar_templated()' 不能重载

但是您可以通过将函数移出类来解决此问题,并且仍然实现专业化:

template<std::size_t size>
void bar_templated() {
    std::cout << "default\n";
}
template<>
void bar_templated<3>() {
    std::cout << "specialized 3\n";
}
template<>
void bar_templated<5>() {
    std::cout << "specialized 5\n";
}


template <typename Container>
struct Point {
    Container container;

    void bar() {
        bar_templated<Container::size>();
    }
};

这样,函数是公开的,但这可能不是你想要的。好吧,如果你在头文件中编写,你可以在匿名命名空间中定义它们。


另外:if constexpr - 但这只是 C++17 及更高版本。它可以大量减少代码大小并保持其逻辑性质,这无疑是这里的最佳方法。

void bar() {
    if constexpr (Container::size == 3) {
        std::cout << "specialized <3>\n";
    }
    else if constexpr (Container::size == 5) {
        std::cout << "specialized <5>\n";
    }
}

【讨论】:

  • 我本可以发誓我尝试过这个,但我收到错误,表明您无法在模板类中专门化模板函数。现在再试一次。
  • 这似乎只适用于 Clang。 GCC 8.2 无法编译。
  • 有什么办法可以用 GCC 编译吗?
  • 我使用了 Visual Studio,所以两个编译器都没有。奇怪的是 gcc 不编译这个。我试图寻找另一种策略
【解决方案3】:

您可以使用SFINAE 应用模板重载。

template <typename C = Container>
auto bar() -> typename std::enable_if<C::size==5>::type { ... }
template <typename C = Container>
auto bar() -> typename std::enable_if<C::size==42>::type { ... }
... ...

LIVE

【讨论】:

  • 我认为bar() 不应该是template
  • 好吧,从 OP 来看,bar() 不是模板。不要问我为什么,问 bremen_matt。
  • @Walter 是的,我知道,我的意思是,如果将其设为模板可能会出现什么问题?
  • @Walter 抱歉无法理解您的意思。您看到我发布的实时示例了吗?
  • 啊,好吧。我错过了你有一个默认的模板参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-01
  • 2019-03-24
  • 2016-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多