使用 constexpr 模板函数时出现各种编译和链接器错误

Various compilation and linker errors while using constexpr template functions

本文关键字:编译 链接 错误 constexpr 函数 使用      更新时间:2023-10-16

下面的代码实现了二分搜索,以查找枚举中是否存在运行时整数值。由于枚举是编译时已知的,因此代码尝试以 constexpr 方式生成枚举值的排序数组,并具有与 constexpr 兼容的查找。

#include <iostream>
#include <type_traits>
// ============================
using std::cout; using std::endl;
constexpr char const * const el = "n";
// ============================
template < typename T_, typename = typename std::enable_if<std::is_enum<T_>::value>::type >
std::ostream & operator<<( std::ostream & os, T_ const & v ) {
    return os << static_cast<typename std::underlying_type<T_>::type>(v)<< " ";
}
// ============================
// Minimum support for a constexpr array because std::array::operator[] lacks constexpr
template <typename T_, size_t N_>
struct Array {
    constexpr size_t size() const { return N_; }
    constexpr T_&       operator[]( size_t i ) { return values_[i]; }
    constexpr T_ const& operator[]( size_t i ) const { return values_[i]; }
    constexpr T_*        begin()       { return values_; }
    constexpr T_*        end()         { return values_+N_; }
    constexpr T_ const * begin() const { return values_; }
    constexpr T_ const * end() const   { return values_+N_; }
    T_ values_[N_];
};
// ============================
// return a sorted copy of the array argument
template <typename T_, size_t N_>
constexpr Array<T_, N_> const_sort( Array<T_, N_> tab ) {
    Array<T_, N_> result{ T_{} };
    for( size_t i{}; i != N_; ++i )
        result[i] = tab[i];
    for( size_t i{}; i != N_-1; ++i ) {
        size_t min {i};
        for( size_t j{ i+1 }; j != N_; ++j ) {
            if( result[j] < result[min] )
                min = j;
        }
        if( min != i ) {
            auto tmp    = std::move( result[i] );
            result[i]   = std::move( result[min] );
            result[min] = std::move( tmp );
        }
    }
    return result;
}
// ============================
// The user has to specialize UnsortedFunc to return an Array<EnumType,N>
template <typename T_>
constexpr auto const UnsortedFunc();
template <typename T_>
constexpr const decltype(UnsortedFunc<T_>()) Unsorted = UnsortedFunc<T_>();
// ============================
template < typename T_ >
constexpr decltype( const_sort( Unsorted<T_> ) ) Sorted = const_sort( Unsorted<T_> );
// ============================
// check for existence of a matching enum member equal to an integral value.
// The user has to feed a specialization of UnsortedFunc with the enum values.
template< typename E_>
constexpr bool Contains( typename std::underlying_type<E_>::type v ) {
    using T = typename std::underlying_type<E_>::type;
    T min = static_cast<T>(Sorted<E_>[0]);
    T max = static_cast<T>(Sorted<E_>[Sorted<E_>.size()-1]);
    if ( v < min || max < v )
        return false;
    size_t low {}, high { Sorted<E_>.size() };
    while ( low < high ) {
        size_t mid = (high + low ) / 2;
        if ( v < static_cast<T>(Sorted<E_>[ mid ]) ) {
            high = mid;
        } else {
            if ( !( static_cast<T>(Sorted<E_>[ mid ]) < v ) )
                return true;
            low = mid+1;
        }
    }
    return false;
}
template < typename T_ >
void Dump() {
    for( auto & v : Unsorted<T_> )
        cout << v << " ";
    cout << el;
    for( auto & v : Sorted<T_> )
        std::cout << v << " ";
    cout << el;
}
enum class Foo : int { a=5, b=2, c = 8, d = 1, e = 4, f = 9 };
template <> constexpr auto const UnsortedFunc<Foo>() { 
    return Array<Foo,6>{ Foo::a, Foo::b, Foo::c, Foo::d, Foo::e, Foo::f }; 
}

通过在 main 函数中使用此代码,我可以有很多不同的警告和错误。

那个编译和链接,但我不得不首先转储两个数组,仍然带有警告:

int main() {
    cout << std::boolalpha << __VERSION__ << el;
    for( auto & v : Unsorted<Foo> )
        cout << v << " ";
    cout << el;
    for( auto & v : Sorted<Foo> )
        std::cout << v << " ";
    cout << el;
    constexpr bool b1 = Contains<Foo>(2);
    constexpr bool b2 = Contains<Foo>(10);
    cout << b1 << " " << b2 << el;
    for( int i{}; i != 10; ++i )
        cout << i << " : " << Contains<Foo>(i) << ", ";
}

和输出:

main.cpp:60:46: warning: variable 'Unsorted<type-parameter-0-0>' has internal linkage but is not defined [-Wundefined-internal]
constexpr const decltype(UnsortedFunc<T_>()) Unsorted = UnsortedFunc<T_>();
                                             ^
main.cpp:64:71: note: used here
constexpr decltype( const_sort( Unsorted<T_> ) ) Sorted = const_sort( Unsorted<T_> );
                                                                      ^
1 warning generated.
4.2.1 Compatible Clang 3.5 (trunk 198621)
5  2  8  1  4  9  
1  2  4  5  8  9  
true false
0 : false, 1 : true, 2 : true, 3 : false, 4 : true, 5 : true, 6 : false, 7 : false, 8 : true, 9 : true,

如果我删除两个数组转储,我有编译错误,b1 和 b2 不能再被评估为 constexpr 表达式:

int main() {
    cout << std::boolalpha << __VERSION__ << el;
    constexpr bool b1 = Contains<Foo>(2);
    constexpr bool b2 = Contains<Foo>(10);
    cout << b1 << " " << b2 << el;
    for( int i{}; i != 10; ++i )
        cout << i << " : " << Contains<Foo>(i) << ", ";
}

输出:

main.cpp:116:20: error: constexpr variable 'b1' must be initialized by a constant expression
    constexpr bool b1 = Contains<Foo>(2);
                   ^    ~~~~~~~~~~~~~~~~
main.cpp:72:28: note: subexpression not valid in a constant expression
    T min = static_cast<T>(Sorted<E_>[0]);
                           ^
main.cpp:116:25: note: in call to 'Contains(2)'
    constexpr bool b1 = Contains<Foo>(2);
                        ^
main.cpp:117:20: error: constexpr variable 'b2' must be initialized by a constant expression
    constexpr bool b2 = Contains<Foo>(10);
                   ^    ~~~~~~~~~~~~~~~~~
main.cpp:72:28: note: subexpression not valid in a constant expression
    T min = static_cast<T>(Sorted<E_>[0]);
                           ^
main.cpp:117:25: note: in call to 'Contains(10)'
    constexpr bool b2 = Contains<Foo>(10);
                        ^
2 errors generated.

最后,如果我尝试使用 Dump 函数转储数组,链接错误也会到来:

int main() {
    cout << std::boolalpha << __VERSION__ << el;
    Dump<Foo>();
}

输出:

main.cpp:60:46: warning: variable 'Unsorted<type-parameter-0-0>' has internal linkage but is not defined [-Wundefined-internal]
constexpr const decltype(UnsortedFunc<T_>()) Unsorted = UnsortedFunc<T_>();
                                             ^
main.cpp:64:71: note: used here
constexpr decltype( const_sort( Unsorted<T_> ) ) Sorted = const_sort( Unsorted<T_> );
                                                                      ^
main.cpp:60:46: warning: variable 'Unsorted<Foo>' has internal linkage but is not defined [-Wundefined-internal]
constexpr const decltype(UnsortedFunc<T_>()) Unsorted = UnsortedFunc<T_>();
                                             ^
main.cpp:93:21: note: used here
    for( auto & v : Unsorted<T_> )
                    ^
main.cpp:64:50: warning: variable 'Sorted<Foo>' has internal linkage but is not defined [-Wundefined-internal]
constexpr decltype( const_sort( Unsorted<T_> ) ) Sorted = const_sort( Unsorted<T_> );
                                                 ^
main.cpp:97:21: note: used here
    for( auto & v : Sorted<T_> )
                    ^
3 warnings generated.
/tmp/main-22b320.o: In function `void Dump<Foo>()':
main.cpp:(.text._Z4DumpI3FooEvv[_Z4DumpI3FooEvv]+0xc): undefined reference to `_ZL8UnsortedI3FooE'
main.cpp:(.text._Z4DumpI3FooEvv[_Z4DumpI3FooEvv]+0x20): undefined reference to `_ZL8UnsortedI3FooE'
main.cpp:(.text._Z4DumpI3FooEvv[_Z4DumpI3FooEvv]+0x98): undefined reference to `_ZL6SortedI3FooE'
main.cpp:(.text._Z4DumpI3FooEvv[_Z4DumpI3FooEvv]+0xb0): undefined reference to `_ZL6SortedI3FooE'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

奖励问题,在已排序和未排序的定义行上,如果我使用 const auto 而不是 decltype :

main.cpp:73:49: error: member reference base type 'const auto' is not a structure or union
    T max = static_cast<T>(Sorted<E_>[Sorted<E_>.size()-1]);
                                      ~~~~~~~~~~^~~~~
main.cpp:77:37: error: member reference base type 'const auto' is not a structure or union
    size_t low {}, high { Sorted<E_>.size() };
                          ~~~~~~~~~~^~~~~
main.cpp:77:20: error: variables defined in a constexpr function must be initialized
    size_t low {}, high { Sorted<E_>.size() };
                   ^
main.cpp:93:19: error: invalid range expression of type 'const auto'; no viable 'begin' function available
    for( auto & v : Unsorted<T_> )
                  ^ ~~~~~~~~
main.cpp:114:5: note: in instantiation of function template specialization 'Dump<Foo>' requested here
    Dump<Foo>();
    ^
main.cpp:97:19: error: invalid range expression of type 'const auto'; no viable 'begin' function available
    for( auto & v : Sorted<T_> )
                  ^ ~~~~~~
5 errors generated.

正如Jarod42所说,这与他发现的错误有关。

使用最新的 clang 主干存放处(版本 4.2.1 兼容 Clang 3.5 (202594) ),一切恢复正常。我只需要编写自己的constexpr std::move,因为Visual studio标头还不符合C++14。

使用该命令行构建正常clang-cl.exe /GR- -D_HAS_EXCEPTIONS=0 /O0 /FA /Wall -Xclang -std=c++1y main.cpp

最终代码:

#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
constexpr char const * const el = "n";
// ============================
// VS2013 do not have constexpr supports in the standard library
namespace std14 {
    template< class T >
    constexpr typename std::remove_reference<T>::type&& move( T&& t ) {
        return static_cast<typename std::remove_reference<T>::type&&>(t);
    }
}
// ============================
// Minimum support for a constexpr array because std::array::operator[] lacks constexpr
template <typename T_, size_t N_>
struct Array {
    constexpr size_t size() const { return N_; }
    constexpr T_&       operator[]( size_t i ) { return values_[i]; }
    constexpr T_ const& operator[]( size_t i ) const { return values_[i]; }
    constexpr T_*        begin()       { return values_; }
    constexpr T_*        end()         { return values_+N_; }
    constexpr T_ const * begin() const { return values_; }
    constexpr T_ const * end() const   { return values_+N_; }
    T_ values_[N_];
};
// ============================
// return a sorted copy of the array argument
template <typename T_, size_t N_>
constexpr Array<T_, N_> const_sort( Array<T_, N_> tab ) {
    Array<T_, N_> result{ { T_{} } };
    for( size_t i{}; i != N_; ++i )
        result[i] = tab[i];
    for( size_t i{}; i != N_-1; ++i ) {
        size_t min {i};
        for( size_t j{ i+1 }; j != N_; ++j ) {
            if( result[j] < result[min] )
                min = j;
        }
        if( min != i ) {
            using std14::move;
            auto tmp    = move(result[i]);
            result[i]   = move(result[min]);
            result[min] = move(tmp);
        }
    }
    return result;
}
// ============================
// The user has to specialize UnsortedFunc to return an Array<EnumType,N>
template <typename T_>
constexpr auto UnsortedFunc();
template <typename T_>
constexpr const decltype(UnsortedFunc<T_>()) Unsorted = UnsortedFunc<T_>();
// ============================
template < typename T_ >
constexpr decltype( const_sort( Unsorted<T_> ) ) Sorted = const_sort( Unsorted<T_> );
// ============================
// check for existence of a matching enum member equal to an integral value.
// The user has to feed a specialization of UnsortedFunc with the enum values.
template< typename E_>
constexpr bool Contains( std::underlying_type_t<E_> v ) {
    using T = std::underlying_type_t<E_>;
    constexpr auto & sorted = Sorted<E_>;
    T min = static_cast<T>(sorted[0]);
    T max = static_cast<T>(sorted[sorted.size()-1]);
    if ( v < min || max < v )
        return false;
    size_t low {}, high { sorted.size() };
    while ( low < high ) {
        size_t mid = (high + low ) / 2;
        if ( v < static_cast<T>(sorted[ mid ]) ) {
            high = mid;
        } else {
            if ( !( static_cast<T>(sorted[ mid ]) < v ) )
                return true;
            low = mid+1;
        }
    }
    return false;
}
enum class Foo : int { a = 5, b = 2, c = 8, d = 1, e = 4, f = 9 };
template <> constexpr auto UnsortedFunc<Foo>() { 
    return Array<Foo,6>{ { Foo::a, Foo::b, Foo::c, Foo::d, Foo::e, Foo::f } }; 
}
int main() {
    cout << std::boolalpha << __VERSION__ << el;
    constexpr bool b1 = Contains<Foo>(2);
    cout << b1 << el;
    for( int i{}; i != 10; ++i ) {
        cout << i << " : " << Contains<Foo>(i) << ", ";
    }   
}