我花一些时间来学习如何在C++中使用模板。我没用过
在此之前,我并不总是确定在不同情况下可以实现或无法实现的目标。

作为练习,我将包装一些用于事件的Blas和Lapack函数,
并且我目前正在研究?GELS的包装(评估线性方程组的解)。

 A x + b = 0
?GELS函数(仅用于实数值)存在两个名称:SGELS,用于单精度 vector 和DGELS具有 double 。

我对接口(interface)的想法是通过这种方式实现的solve函数:
 const std::size_t rows = /* number of rows for A */;
 const std::size_t cols = /* number of cols for A */;
 std::array< double, rows * cols > A = { /* values */ };
 std::array< double, ??? > b = { /* values */ };  // ??? it can be either
                                                  // rows or cols. It depends on user
                                                  // problem, in general
                                                  // max( dim(x), dim(b) ) =
                                                  // max( cols, rows )
 solve< double, rows, cols >(A, b);
 // the solution x is stored in b, thus b
 // must be "large" enough to accomodate x

根据用户要求,问题可能是过高确定的,也可能是不确定的,这意味着:
  • (如果它是超定的)dim(b) > dim(x)(解决方案是伪逆)
  • (如果未确定)dim(b) < dim(x)(解决方案是最小化LSQ)
  • dim(b) = dim(x)(解决方案是A的反函数)的正常情况

  • (不考虑单个情况)。

    由于?GELS将结果存储在输入 vector b中,因此std::array应该
    如代码注释(max(rows, cols))中所述,有足够的空间来容纳解决方案。

    我想(编译时)确定采用哪种解决方案(这是参数更改)
    ?GELS调用中)。我有两个功能(为方便起见,我正在简化),
    处理精度并已经知道b的维数和rows/cols的数量:
    namespace wrap {
    
    template <std::size_t rows, std::size_t cols, std::size_t dimb>
    void solve(std::array<float, rows * cols> & A, std::array<float, dimb> & b) {
      SGELS(/* Called in the right way */);
    }
    
    template <std::size_t rows, std::size_t cols, std::size_t dimb>
    void solve(std::array<double, rows * cols> & A, std::array<double, dimb> & b) {
      DGELS(/* Called in the right way */);
    }
    
    }; /* namespace wrap */
    

    是内部包装的一部分。用户功能,确定所需的大小
    通过模板在b vector 中:
    #include <type_traits>
    
    /** This struct makes the max between rows and cols */
    template < std::size_t rows, std::size_t cols >
    struct biggest_dim {
      static std::size_t const value = std::conditional< rows >= cols, std::integral_constant< std::size_t, rows >,
                                                         std::integral_constant< std::size_t, cols > >::type::value;
    };
    
    /** A type for the b array is selected using "biggest_dim" */
    template < typename REAL_T, std::size_t rows, std::size_t cols >
    using b_array_t = std::array< REAL_T, biggest_dim< rows, cols >::value >;
    
    /** Here we have the function that allows only the call with b of
     *  the correct size to continue */
    template < typename REAL_T, std::size_t rows, std::size_t cols >
    void solve(std::array< REAL_T, cols * rows > & A, b_array_t< REAL_T, cols, rows > & b) {
      static_assert(std::is_floating_point< REAL_T >::value, "Only float/double accepted");
      wrap::solve< rows, cols, biggest_dim< rows, cols >::value >(A, b);
    }
    

    这样实际上可以工作。但是我想进一步走下去,而且我真的不知道如何做。
    如果用户尝试使用大小过小的solve来调用b,则编译器会引发极其难以阅读的错误。

    我正在尝试插入
    可以帮助用户理解其错误的static_assert。但是我想到的任何方向
    需要使用具有相同签名的两个函数(就像模板重载吗?)
    我找不到SFINAE策略(它们实际上根本不编译)。

    您是否认为在编译时不更改用户界面的情况下,可以为b维度为的错误引发静态声明?
    我希望这个问题足够清楚。

    @Caninonos :对我而言,用户界面是用户调用求解器的方式,即:
     solve< type, number of rows, number of cols > (matrix A, vector b)
    

    为了提高我的技能,这是我锻炼的一个限制。这意味着,我不知道是否有可能实现该解决方案。 b的类型必须与函数调用匹配,并且如果添加另一个模板参数并且更改用户界面(违反约束)很容易。

    最小的完整的工作示例

    这是一个最小的完整且可行的示例。根据要求,我删除了对线性代数概念的任何引用。这是个数字问题。情况是:
  • N1 = 2, N2 =2。由于N3 = max(N1, N2) = 2一切正常,所以
  • N1 = 2, N2 =1。由于N3 = max(N1, N2) = N1 = 2一切正常,所以
  • N1 = 1, N2 =2。由于N3 = max(N1, N2) = N2 = 2一切正常,所以
  • N1 = 1, N2 =2。由于N3 = N1 = 1 < N2,它会正确引发编译错误。我想用一个静态断言来拦截编译错误,该断言解释了N3的维数错误的事实。就目前而言,该错误难以阅读和理解。

  • 你可以view and test it online here

    最佳答案

    首先,进行一些改进以简化设计并帮助实现可重复性:

  • 不需要biggest_dimstd::max是C++ 14的constexpr。您应该改用它。
  • 不需要b_array_t。你可以只写std::array< REAL_T, std::max(N1, N2)>

  • 现在解决您的问题。 C++ 17中的一种好方法是:
    template < typename REAL_T, std::size_t N1, std::size_t N2, std::size_t N3>
    void solve(std::array< REAL_T, N1 * N2 > & A, std::array< REAL_T, N3> & b) {
    
        if constexpr (N3 == std::max(N1, N2))
            wrap::internal< N1, N2, N3 >(A, b);
        else
            static_assert(N3 == std::max(N1, N2), "invalid 3rd dimmension");
    
            // don't write static_assert(false)
            // this would make the program ill-formed (*)
    }
    

    或者,如@ max66所指出的
    template < typename REAL_T, std::size_t N1, std::size_t N2, std::size_t N3>
    void solve(std::array< REAL_T, N1 * N2 > & A, std::array< REAL_T, N3> & b) {
    
        static_assert(N3 == std::max(N1, N2), "invalid 3rd dimmension");
    
        if constexpr (N3 == std::max(N1, N2))
            wrap::internal< N1, N2, N3 >(A, b);
    
    }
    

    Tadaa !! 简单,优雅,不错的错误消息。

    constexpr if版本和仅static_assert之间的区别,即:
    void solve(...)
    {
       static_assert(...);
       wrap::internal(...);
    }
    

    是仅使用static_assert,即使wrap::internal失败,编译器也会尝试实例化static_assert,从而污染了错误输出。如果条件条件下对wrap::internal的调用不是主体的一部分,则使用constexpr失败,因此错误输出是干净的。

    (*)我之所以不写static_asert(false, "error msg)的原因是,这会使程序格式错误,不需要进行诊断。参见constexpr if and static_assert

    如果需要,还可以通过将模板参数移到不可扣除的后面来使float/double可扣除:
    template < std::size_t N1, std::size_t N2, std::size_t N3,  typename REAL_T>
    void solve(std::array< REAL_T, N1 * N2 > & A, std::array< REAL_T, N3> & b) {
    

    因此,该调用变为:
    solve< n1_3, n2_3>(A_3, b_3);
    

    关于c++ - 模板函数重载和SFINAE实现,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49171904/

    10-11 22:55
    查看更多