Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using result of constexpr function as a template parameter (clang vs gcc)

Please take a look at the code below, sorry that is a bit lengthy, but I did my best to reproduce the problem with a minimum example (there is also a live copy of it). There I basically have a metafunction which returns the size of string literal, and constexpr function which wraps it. Then when I call those functions in a template parameter gcc (5.4, 6.2) is happy with it, but clang (3.8, 3.9) barfs with "non-type template argument is not a constant expression" in test body on strsize(s). If I replace with a str_size<S> both compilers are happy. So the questions are:

  1. whether that is a problem with clang, or my code?

  2. What is the way to make it compile on both clang and gcc with constexpr function?

    template<size_t N> using string_literal_t = char[N];
    
    template<class T> struct StrSize; ///< metafunction to get the size of string literal alikes 
    
    /// specialize StrSize for string literals
    template<size_t N>
    struct StrSize <string_literal_t<N>>{ static constexpr size_t value = N-1; };
    
    /// template variable, just for convenience
    template <class T>
    constexpr size_t str_size = StrSize<T>::value;
    
    /// now do the same but with constexpr function
    template<class T>
    constexpr auto strsize(const T&) noexcept-> decltype(str_size<T>) {
       return str_size<T>;
    }
    
    template<class S, size_t... Is>
    constexpr auto test_helper(const S& s, index_sequence<Is...>) noexcept-> array<char, str_size<S>> {
       return {s[Is]...};
    }
    
    template<class S>
    constexpr auto test(const S& s) noexcept-> decltype(auto) {
    // return test_helper(s, make_index_sequence<str_size<S>>{}); // this work in both clang and gcc
       return test_helper(s, make_index_sequence<strsize(s)>{});  // this works only in gcc
    }
    
    auto main(int argc, char *argv[])-> int {
       static_assert(strsize("qwe") == 3, "");
       static_assert(noexcept(test("qwe")) == true, "");
    
       return 0;
    }
    
like image 420
Slava Avatar asked Mar 28 '17 14:03

Slava


1 Answers

Clang is correct here. The problem is in the code and in GCC, which erroneously accepted it. This was fixed in GCC 10: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66477

According to the standard expr.const#5.12:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine, would evaluate one of the following: ... an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either

  • it is usable in constant expressions or
  • its lifetime began within the evaluation of E; And here the compiler is unable to verify the validity of reference in test(const S& s).

Actually there is a nice article to read here: https://brevzin.github.io/c++/2020/02/05/constexpr-array-size/

As to your other question:

What is the way to make it compile on both clang and gcc with constexpr function?

You can replace references with std::array passed by value:

#include <array>
using namespace std;

template<class T> struct StrSize;

template<size_t N>
struct StrSize <array<char,N>>{ static constexpr size_t value = N-1; };

template <class T>
constexpr size_t str_size = StrSize<T>::value;

template<class T>
constexpr auto strsize(const T&) noexcept-> decltype(str_size<T>) {
   return str_size<T>;
}

template<class S, size_t... Is>
constexpr auto test_helper(const S& s, index_sequence<Is...>) noexcept-> array<char, str_size<S>> {
   return {s[Is]...};
}

constexpr auto test(array<char,4> s) noexcept-> decltype(auto) {
   return test_helper(s, make_index_sequence<strsize(s)>{});
}

int main() {
   static_assert(noexcept(test({"qwe"})) == true, "");
}

Demo: https://gcc.godbolt.org/z/G8zof38b1

like image 185
Fedor Avatar answered Sep 21 '22 11:09

Fedor