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:
whether that is a problem with clang, or my code?
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;
}
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
Related
I have the following template / compile-time utility helper functions. It works fine in all three compilers (MSVC, GCC, clang) when everything is constexpr, but changing a few bits to consteval results in an odd error in MSVC. I want to migrate all my constexpr machinery to consteval as much as possible, and this problem is not helping.
#include <optional>
#include <tuple>
#include <type_traits>
template <auto v>
struct value_as_type {
static constexpr auto value = v;
using type = decltype(value);
consteval operator type() const noexcept {
return v;
}
};
template <size_t First, size_t Last, typename Functor>
consteval void consteval_for([[maybe_unused]] Functor&& f) noexcept
{
if constexpr (First < Last)
{
if constexpr (std::is_same_v<bool, std::invoke_result_t<Functor()>>)
{
if (f(value_as_type<First>{}) == false)
return;
}
else
f(value_as_type<First>{});
consteval_for<First + 1, Last>(std::forward<Functor>(f));
}
}
template <size_t index, typename... Args>
using type_by_index = std::tuple_element_t<index, std::tuple<Args...>>;
template <typename T, typename... Args>
[[nodiscard]] consteval std::optional<size_t> index_for_type() noexcept
{
std::optional<size_t> index;
consteval_for<0, sizeof...(Args)>([&index](auto i) {
if (!index) // The index of the first occurrence is stored
{
if constexpr (std::is_same_v<T, type_by_index<static_cast<size_t>(i), Args... >> )
index = static_cast<size_t>(i);
}
});
return index;
}
static_assert(index_for_type<int, float, void, int, bool>() == 2);
The error message is:
<source>(52): error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'std::optional<size_t>'
<source>(52): note: Invalid aggregate initialization
I'm not seeing any aggregate initialization of the optional at all. And my question is: do you think it's a bug in MSVC, or is there something not right with my code?
P. S. Removing optional and just returning size_t removes the error, but I do need a way to say "the type is not present in the pack" without compilation error. std::optional seems the perfect fit for this semantically.
The same error occurs with all the guts removed.
#include <optional>
[[nodiscard]] consteval std::optional<size_t> index_for_type() noexcept
{
return 1;
}
static_assert(index_for_type() == 2);
This leaves me to believe that Microsoft's std::optional is simply not consteval-ready yet.
The following code compiles in gcc 9.3, but not in gcc 10.2:
constexpr std::array<int, 2> opt = {1,2};
template <typename T>
constexpr auto f(const T& arr)
{
std::array<int, arr.size()> res{};
return res;
}
int main()
{
auto res = f(opt);
}
The code is in https://godbolt.org/z/8hb6M8.
The error given by gcc10.2 is that arr.size() is not a constant expression.
Which compiler is right? 9.3 or 10.2?
If 10.2 is right, how can I define a compile time array and pass its size (and the array) as an argument?
Not sure which one is right but for
how can I define a compile time array and pass its size (and the array) as an argument?
You can change the function to
template <typename T, std::size_t N>
constexpr auto f(const std::array<T, N>& arr)
{
std::array<int, N> res{};
return res;
}
and now the size gets uplifted into the template parameter.
Another alternative which works in both compilers and doesn't require changing the template declaration:
std::array<int, std::tuple_size_v<T>> res{};
The definition of constant expressions has been changed since C++20, you can find several changes on this.
The shorter answer is to change your function signature:
template <typename T>
constexpr auto f(const T& arr);
into:
template <typename T>
constexpr auto f(const T arr);
Then it works.
I am attempting to create the equivalent of the Visual Studio _countof macro using C++ templates. The following are my proposed definitions:
template<typename T, size_t N>
inline constexpr size_t countof(T const (&array)[N]) {
return N;
}
template<typename T, typename U, size_t N>
inline constexpr size_t countof(T const (U::&array)[N]) {
return N;
}
The second declaration above was an attempt to fix the following code, which generates a compile-time error in g++ 9 with the message: "error: invalid use of non-static data member ‘foo::bar’":
struct foo {
int const bar[4];
static_assert(countof(bar) == 4);
};
However, when I add the second definition, and change the assertion to use foo::bar, g++ generates the error: "error: ‘template constexpr const size_t countof’ conflicts with a previous declaration".
I can change the code to use pointer-to-member (instead of reference to member), but that seems like it should be unnecessary. Does anyone know of a way to make a version of countof that only compiles when passed an array, and works in a reasonable way for both free and member variable arrays?
The problem is the usage of bar is invalid in static_assert(countof(bar) == 4);, you need an instance of foo and get the member array bar to pass to countof.
I can change the code to use pointer-to-member (instead of reference to member), but that seems like it should be unnecessary.
You can change the code to use pointer-to-member. e.g.
template<typename T, typename U, size_t N>
inline constexpr size_t countof(T const (U::*array)[N]) {
return N;
}
then
static_assert(countof(&foo::bar) == 4);
LIVE
Or change countof to specify the type instead of passing the array to it.
template<typename T>
struct count_of{};
template<typename T, size_t N>
struct count_of<T const [N]> {
constexpr static size_t value = N;
};
template<typename T>
inline constexpr size_t countof() {
return count_of<T>::value;
}
then
static_assert(countof<decltype(foo::bar)>() == 4);
LIVE
I couldn't figure out a way to do this without macros, but this post provides a way to get both type-safety (ensuring the argument passed to countof is an array) and supports both free and member arrays. The resulting code is:
template<typename T, size_t N>
char (&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T (&array)[N]))[N];
#define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x))
I have the function hash_constexpr that takes in a const char* and returns a hash using a novel algorithm. The hash_constexpr function should be generating the hash at compile time.
namespace detail
{
template<size_t Count>
inline constexpr size_t countof(const char(&string)[Count])
{
return Count - 1;
}
template<typename T>
struct ascii_hash_t
{
template<typename L>
static constexpr T f(L const& data, T hash, size_t i = 0)
{
return i < countof(data) ? f(data, (hash & (~0u)) ^ (hash << 7) ^ T(data[i]), i + 1) : hash;
}
};
template<typename T, typename L>
inline constexpr T generate_ascii_hash(L const& data)
{
return detail::ascii_hash_t<T>::f(data, 0);
}
};
template<size_t Count>
inline constexpr uint32_t hash_constexpr(const char(&string)[Count])
{
return detail::generate_ascii_hash<uint32_t>(string);
}
My issue is that it appears that the hash_constexpr function doesn't appear to actually be returning a constexpr value. When I invoke it like so:
constexpr uint32_t asd = hash_constexpr("asdasd");
I get the following error:
Constexpr variable 'asd' must be initialized by a constant expression
What am I doing wrong?
EDIT #1:
Note that this call is working correctly:
constexpr int32_t countof_test = detail::countof("hello");
EDIT #2:
It appears that this call is working correctly as well:
constexpr int32_t generate_ascii_hash_test = detail::generate_ascii_hash<int32_t>("asd");
The issue is that the function ascii_hash_t::f, for the specific template instantiation is not a constexpr. This prevents you from performing the operation as a constexpr.
See http://ideone.com/heFuFP for an example if it working as you expect.
Being not declared constexpr, std::forward will discard constexpr-ness for any function it forwards arguments to. Why is std::forward not declared constexpr itself so it can preserve constexpr-ness?
Example: (tested with g++ snapshot-2011-02-19)
#include <utility>
template <typename T> constexpr int f(T x) { return -13;}
template <typename T> constexpr int g(T&& x) { return f(std::forward<T>(x));}
int main() {
constexpr int j = f(3.5f);
// next line does not compile:
// error: ‘constexpr int g(T&&) [with T = float]’ is not a constexpr function
constexpr int j2 = g(3.5f);
}
Note: technically, it would be easy to make std::forward constexpr, e.g., like so (note that in g std::forward has been replaced by fix::forward):
#include <utility>
namespace fix {
/// constexpr variant of forward, adapted from <utility>:
template<typename Tp>
inline constexpr Tp&&
forward(typename std::remove_reference<Tp>::type& t)
{ return static_cast<Tp&&>(t); }
template<typename Tp>
inline constexpr Tp&&
forward(typename std::remove_reference<Tp>::type&& t)
{
static_assert(!std::is_lvalue_reference<Tp>::value, "template argument"
" substituting Tp is an lvalue reference type");
return static_cast<Tp&&>(t);
}
} // namespace fix
template <typename T> constexpr int f(T x) { return -13;}
template <typename T> constexpr int g(T&& x) { return f(fix::forward<T>(x));}
int main() {
constexpr int j = f(3.5f);
// now compiles fine:
constexpr int j2 = g(3.5f);
}
My question is: why is std::forward not defined like fix::forward ?
Note2: this question is somewhat related to my other question about constexpr std::tuple as std::forward not being constexpr is the technical reason why std::tuple cannot be created by calling its cstr with rvalues, but this question here obviously is (much) more general.
The general answer is that the C++ committee's Library Working Group have not done an exhaustive trawl through the working draft looking for opportunities to use the new core facilities. These features have been used where people have had the time and inclination to look at possible uses, but there is not the time for exhaustive checking.
There are some papers regarding additional uses of constexpr in the works, such as those in the November 2010 mailing.