我编写了一个矩阵类模板,其中包括专门用于某些类模板参数的算术运算符,这些参数使用 if constexpr 语句。如果使用Visual C++ 编译器(版本 19.00.24245)编译,则在另一个模板类中使用此矩阵类可能会导致在初始化之前无法使用形式为“符号”的大量错误消息。如果矩阵类模板在另一个类模板中使用,然后在 Linux 上使用 g++ 7.4.0 或最新的 Apple clang 版本编译,或者如果在非类模板中使用,然后使用 Visual-C++ 编译器编译,编译成功。
为了能够重现上述问题,下面给出了包含矢量类模板和一个运算符的代码段,该运算符模仿了上述矩阵类的结构、编译器标志以及错误消息。注释掉第 160 行并将注释形式的第 185 行切换到第 186 行会导致编译情况。
#include <iostream>
#include <type_traits>
enum class Layout
{
standard,
reverse
};
template< typename T_,
std::size_t numEls_,
Layout layout_ = Layout::standard >
class Vec
{
public:
using ValueType = T_;
static constexpr auto numElements = numEls_;
static constexpr auto layout = layout_;
Vec() = default;
template< typename... Ts >
Vec( Ts... vals )
: els_{}
{
static_assert( sizeof...( Ts ) == numEls_ );
T_ tmp[] = { vals... };
if constexpr ( layout_ == Layout::standard )
{
for ( auto i = decltype( numEls_ ){ 0 }; i < numEls_; ++i )
{
els_[ i ] = tmp[ i ];
}
}
else // laylout == Layout::reverse
{
auto j = numEls_;
for ( auto i = decltype( numEls_ ){ 0 }; i < numEls_; ++i )
{
els_[ i ] = tmp[ --j ];
}
}
}
private:
template< typename Rhs >
void AssignFromRhs( const Rhs& rhs )
{
static_assert( std::is_same_v< typename Rhs::ValueType, T_ > );
static_assert( Rhs::numElements == numEls_ );
auto tmp = rhs.Data();
if constexpr ( Rhs::layout == layout_ )
{
for ( auto i = decltype( numEls_ ){ 0 }; i < numEls_; ++i )
{
els_[ i ] = tmp[ i ];
}
}
else // Rhs::layout != layout_
{
auto j = numEls_;
for ( auto i = decltype( numEls_ ){ 0 }; i < numEls_; ++i )
{
els_[ i ] = tmp[ --j ];
}
}
}
public:
template< typename Rhs >
Vec( const Rhs& rhs )
{
AssignFromRhs( rhs );
}
template< typename Rhs >
Vec& operator=( const Rhs& rhs )
{
AssignFromRhs( rhs );
return *this;
}
void Cout() const
{
std::cout << "[";
if constexpr ( layout_ == Layout::standard )
{
for ( auto i = decltype( numEls_ ){ 0 }; i < numEls_; ++i )
{
std::cout << " " << els_[ i ];
}
}
else // laylout == Layout::reverse
{
for ( auto i = decltype( numEls_ ){ numEls_ }; i > 0; )
{
std::cout << " " << els_[ --i ];
}
}
std::cout << " ]" << std::endl;
}
T_* Data()
{
return els_;
}
const T_* Data() const
{
return els_;
}
template< typename Rhs >
auto operator+( const Rhs& rhs )
{
static_assert( std::is_same_v< typename Rhs::ValueType, T_ > );
static_assert( Rhs::numElements == numEls_ );
Vec res;
auto tmp = rhs.Data();
if constexpr ( Rhs::layout == layout_ )
{
for ( auto i = decltype( numEls_ ){ 0 }; i < numEls_; ++i )
{
res.els_[ i ] = els_[ i ] + tmp[ i ];
}
}
else // Rhs::layout != layout_
{
auto j = numEls_;
for ( auto i = decltype( numEls_ ){ 0 }; i < numEls_; ++i )
{
res.els_[ i ] = els_[ i ] + tmp[ --j ];
}
}
return res;
}
private:
T_ els_[ numEls_ ];
};
using VecD3S = Vec< double, 3 >;
using VecD3R = Vec< double, 3, Layout::reverse >;
template< int dummy >
class Foo
{
public:
void DoSomething()
{
v3_ = v1_ + v2_;
}
VecD3S v1_, v3_;
VecD3R v2_;
};
int main( const int argc, const char* argv[] )
{
VecD3S v1 = { 1., 2., 3. };
VecD3R v2 = { 11., 12., 13. };
v1.Cout();
v2.Cout();
auto v3 = v1 + v2;
v3.Cout();
//Foo foo;
Foo< 1 > foo;
foo.v1_ = v1;
foo.v2_ = v2;
foo.v1_.Cout();
foo.v2_.Cout();
foo.DoSomething();
foo.v3_.Cout();
return 0;
}
/JMC /permissive- /GS /W3 /Zc:wchar_t /ZI /Gm- /Od /sdl /Fd"x64\Debug\vc142.pdb" /Zc:inline /fp:precise /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /std:c++17 /FC /Fa"x64\Debug\" /EHsc /nologo /Fo"x64\Debug\" /Fp"x64\Debug\TplTplAutoTest.pch" /diagnostics:column
Error C3536 'i': cannot be used before it is initialized [... line] 135
Error C3536 'tmp': cannot be used before it is initialized [... line] 137
Error C2109 subscript requires array or pointer type [... line] 137
Error C3536 'i': cannot be used before it is initialized [... line] 144
Error C3536 'j': cannot be used before it is initialized [... line] 146
Error C2109 subscript requires array or pointer type [... line] 146
我的第一个问题是上面的代码是否有问题,即它不符合 C++17 标准,或者编译器标志是否丢失,或者编译错误是由于 Visual- C++ 编译器?此外,我在问,如何修改代码以使用 Visual Studio 编译器进行编译而不改变其一般结构,例如,放弃使用 if constexpr 语句?(不使用模板特化(在矩阵类中)的原因是它会显着增加代码大小,因为需要对多个模板参数进行特化并且还接受代理类作为参数。)