【问题标题】:Preventing conversion operators from compiling unless a static condition is met除非满足静态条件,否则阻止转换运算符编译
【发布时间】:2014-08-30 16:28:16
【问题描述】:

我有 Vector (CVector<T, std::size_t Size>)、Matrix (CMatrix<T, std::size_t Height, std::size_t Width>) 和 Tensor (CTensor<T, std::size_t... Sizes>) 类,我希望能够从 CTensor 类隐式转换为 CVector 类,如果 @ 987654326@ 和CMatrix 类如果sizeof...(Sizes) == 2,所以我有以下转换运算符(最初我没有std::enable_if 模板参数,希望我可以使用SFINAE 来防止它编译):

template <typename std::enable_if<sizeof...(Sizes) == 2, int>::type = 0>
operator CMatrix<NumType, Sizes...>() const
{
    static_assert(sizeof...(Sizes) == 2, "You can only convert a rank 2 tensor to a matrix");

    CMatrix<NumType, Sizes...> matResult;

    auto& arrThis = m_numArray;
    auto& arrResult = matResult.m_numArray;
    concurrency::parallel_for_each( arrResult.extent, [=, &arrThis, &arrResult]( concurrency::index<2> index ) restrict( amp ) {
        arrResult[index] = arrThis[index];
    } );

    return matResult;
}

template <typename std::enable_if<sizeof...(Sizes) == 1, int>::type = 0>
operator CVector<NumType, Sizes...>() const
{
    static_assert(sizeof...(Sizes) == 1, "You can only convert a rank 1 tensor to a vector");

    CVector<NumType, Sizes...> vecResult;

    auto& arrThis = m_numArray;
    auto& arrResult = vecResult.m_numArray;
    concurrency::parallel_for_each( arrResult.extent, [=, &arrThis, &arrResult]( concurrency::index<1> index ) restrict( amp ) {
        arrResult[index] = arrThis[index];
    } );

    return vecResult;
}

但是,例如,如果我实例化 CTensor&lt;float, 3, 3, 3&gt; 并尝试编译,则会出现错误,声明 CMatrixCVector 的模板参数过多,以及有关 @987654334 缺少类型的错误@。有没有一种方法可以实现这些运算符,而无需专门针对 1 级和 2 级的 CTensor

【问题讨论】:

  • 我真的不知道任何其他方式。试图解决类似的问题并以 base implementation 结束,然后将最终类声明为从基类派生并专门针对其他情况。顺便说一句:为什么不只使用 CTensor 并将其别名为 CVector / CMatrix?无需转换,它们将变得相同。
  • @firda 这不是一个坏主意;我会拭目以待,看看其他人是否有好主意(以帮助防止我不得不重构),如果没有,我肯定会这样做;谢谢!!
  • 快速注意,你可以抛出异常,如果你真的不希望它编译你限制自己编译时间的东西,所以不是用户输入。 (张量和向量,我猜你需要用户输入)

标签: c++ templates c++11 sfinae


【解决方案1】:

我已经简化了我之前的解决方案,详情如下。

根本不需要SFINAE,因为您在模板方法中有static_assert,它仅在使用时实例化。

我的解决方案使转换运算符成为具有 依赖参数 的模板方法(这样编译器不会实例化其主体,仅解析签名),并添加 -1 大小,假装在其中缺少维度大小为 1 的张量(不是张量本身,而是提取参数包的辅助类),以允许编译器实例化张量模板本身,但不允许稍后在无效维度的张量内实例化转换运算符。

Live demo link.

#include <cstddef>

template <typename T, unsigned int index, T In, T... args>
struct GetArg
{
    static const T value = GetArg<T, index-1, args...>::value;
};

template <typename T, T In, T... args>
struct GetArg<T, 0, In, args...>
{
    static const T value = In;
};

template <typename T, T In>
struct GetArg<T, 1, In>
{
    static const T value = -1;
};

template <typename T, std::size_t Size>
struct CVector
{
};

template <typename T, std::size_t Height, std::size_t Width>
struct CMatrix
{
};

template <typename T, std::size_t... Sizes>
struct CTensor 
{
    template <std::size_t SZ = sizeof...(Sizes)>
    operator CVector<T, GetArg<std::size_t, 0, Sizes...>::value>() const
    {
        static_assert(SZ == 1, "You can only convert a rank 1 tensor to a vector");
        CVector<T, Sizes...> vecResult;
        return vecResult;
    }

    template <std::size_t SZ = sizeof...(Sizes)>
    operator CMatrix<T, GetArg<std::size_t, 0, Sizes...>::value, GetArg<std::size_t, 1, Sizes...>::value>() const
    {
        static_assert(SZ == 2, "You can only convert a rank 2 tensor to a matrix");
        CMatrix<T, Sizes...> matResult;
        return matResult;
    }
};

int main()
{
    CTensor<float, 3> tensor3;
    CTensor<float, 3, 3> tensor3_3;
    CTensor<float, 3, 3, 3> tensor3_3_3;
    CVector<float, 3> vec(tensor3);
    //CVector<float, 3> vec2(tensor3_3); // static_assert fails!
    CMatrix<float, 3, 3> mat(tensor3_3);
    //CMatrix<float, 3, 3> mat2(tensor3_3_3); // static_assert fails!
}

【讨论】:

  • @Shaktal:我刚刚更新了我的解决方案,请看,现在更好了,因为它使用 static_assert 而不是 SFINAE
【解决方案2】:

您可以通过以下方式使用static_assert

template <typename NumType,size_t... Sizes>
struct CTensor {
    template<size_t n,size_t m>
    operator CMatrix<NumType,n,m>() const
    {
        static_assert(
            sizeof...(Sizes)==2,
            "You can only convert a rank 2 tensor to a matrix"
        );
        static_assert(
            std::is_same<CTensor<NumType,n,m>,CTensor>::value,
            "Size mismatch"
        );

        ...    
    }

    template<size_t n>
    operator CVector<NumType,n>() const
    {
        static_assert(
            sizeof...(Sizes)==1,
            "You can only convert a rank 1 tensor to a vector"
        );
        static_assert(
            std::is_same<CTensor<NumType,n>,CTensor>::value,
            "Size mismatch"
        );

        ...    
    }
};

或与 SFINAE:

template <typename NumType,size_t... Sizes>
struct CTensor {
    template<size_t n,size_t m,
      typename =
        typename std::enable_if<
          std::is_same<CTensor<NumType,n,m>,CTensor>::value, int
        >::type
    >
    operator CMatrix<NumType,n,m>() const
    {
        ...
    }

    template<size_t n,
      typename =
        typename std::enable_if<
          std::is_same<CTensor<NumType,n>,CTensor>::value, int
        >::type
    >
    operator CVector<NumType,n>() const
    {
        ...
    }
};

这是另一种使用函数重载的方法:

template <typename NumType,size_t... Sizes>
struct CTensor {
    template<size_t n,size_t m>
    CMatrix<NumType,n,m> convert() const
    {
        ...
    }

    template<size_t n>
    CVector<NumType,n> convert() const
    {
        ...
    }

    template <typename T>
    operator T() const { return convert<Sizes...>(); }
};

【讨论】:

  • @PiotrS.:我不确定static_assert 是否真的是我们想要的。这似乎是一种解决方法:“最初我没有 std::enable_if 模板参数,希望我可以使用 SFINAE 来防止它编译”。但是,static_assert 可以很容易地使用。我会添加一个版本。
  • @PiotrS.:带有enable_if 的新版本对我来说似乎是一种更正常的方法,它不涉及创建更多的帮助类。
  • @VaughnCato:目前的第二种方法看起来很棒而且很简单
  • @PiotrS.:谢谢。那我把static_assert版本放在最上面。
【解决方案3】:

这实际上是对我的评论的更长描述:为什么不只使用 CTensor 并将其别名为 CVector / CMatrix?无需转换,它们将变得相同。
...它以与标题所要求的完全不同的方式解决了真正的问题。仅作记录:)


1) 在命名空间detail中隐藏基本实现
2) 专门化真正需要专门化的东西
(这也可以通过一些辅助结构来完成 - 专门化提供方法的结构)
3) 别名 CVector/CMatrix作为CTensor (那么就不需要算子了)

#include <vector>

namespace detail {

template<class T, std::size_t... Sizes>
  class base;
template<class T, std::size_t Size>
  class base<T, Size> {
    std::vector<T> data;
public:
    T& operator[](std::size_t i) {
        return data[i]; }
};
template<class T, std::size_t First, std::size_t... More>
  class base<T, First, More...> {
    std::vector<base<T, More...>> data;
public:
//  this could be done better, just an example
    base<T, More...>& operator[](std::size_t i) {
        return data[i]; }
};

}

template<class T, std::size_t... Sizes>
  class CTensor: public detail::base<T, Sizes...> {};
//we can specialize CTensor<T, Size>
//and CTensor<T, Width, Height> here
template<class T, std::size_t Size>
  using CVector = CTensor<T, Size>;
template<class T, std::size_t Width, std::size_t Height>
  using CMatrix = CTensor<T, Width, Height>;

【讨论】:

  • 恕我直言,对我来说它并没有解决题为:“除非满足静态条件,否则防止转换运算符编译”的问题,只有这个特定的
  • 如果事实证明真正的问题可能以与标题所述完全不同的方式解决,我该怎么办?
【解决方案4】:

使sizeof...(Sizes) 成为依赖参数,并使CMatrix/CVector 类型正确(采用正确数量的模板参数)。

使用:

template <std::size_t ... Is> struct index_sequence {};

template <std::size_t I, typename T> struct index_element;

template <std::size_t I, std::size_t ... Is>
struct index_element<I, index_sequence<Is...> >
{
private:
    static constexpr const std::size_t a[] = {Is...};
public:
    static_assert(I < sizeof...(Is), "out of bound");
    static constexpr const std::size_t value = a[I];
};

那么你可以这样做:

template <
    std::size_t N = sizeof...(Sizes),
    typename std::enable_if<N == 1, int>::type = 0>
operator CVector<
    T,
    index_element<0, index_sequence<Sizes..., 0>
    >::value>() const
{
    // Your implementation
}

template <
    std::size_t N = sizeof...(Sizes),
    typename std::enable_if<N == 2, int>::type = 0>
operator CMatrix<
    T,
    index_element<0, index_sequence<Sizes..., 0>>::value
    index_element<1, index_sequence<Sizes..., 0, 0>>::value
    >() const
{
    // Your implementation
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-26
    • 2023-04-05
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    • 2020-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多