The following code tries to make compile-time decisions based on the last argument passed in a parameter pack. It contains a comparison if the number of parameter-pack arguments is > 0 and then tries to get the last element of it. However, the constructed tuple is accessed at an invalid index which is supposedly bigger than the max tuple index (as the static_assert shows).
How is that possible if I do cnt-1?
Demo
#include <cstdio>
#include <concepts>
#include <utility>
#include <tuple>
template <typename... Args>
auto foo(Args&&... args)
{
auto tuple = std::forward_as_tuple(std::forward<Args>(args)...);
constexpr std::size_t cnt = sizeof...(Args);
if constexpr (cnt > 0 && std::same_as<std::remove_cvref_t<std::tuple_element_t<cnt-1, decltype(tuple)>>, int>) {
printf("last is int\n");
} else {
printf("last is not int\n");
}
}
int main()
{
foo(2);
foo();
}
Error:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/tuple: In instantiation of 'struct std::tuple_element<18446744073709551615, std::tuple<> >':
<source>:13:25: required from 'auto foo(Args&& ...) [with Args = {}]'
<source>:24:8: required from here
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/tuple:1357:25: error: static assertion failed: tuple index must be in range
1357 | static_assert(__i < sizeof...(_Types), "tuple index must be in range");
| ~~~~^~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/tuple:1357:25: note: the comparison reduces to '(18446744073709551615 < 0)'
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/tuple:1359:13: error: no type named 'type' in 'struct std::_Nth_type<18446744073709551615>'
1359 | using type = typename _Nth_type<__i, _Types...>::type;
| ^~~~
Short-circuiting that stops the rhs from being evaluated (having its value computed at runtime) doesn't stop it from being instantiated (having template arguments substituted into templates, checking them for validity, all at compile-time).
I don't see any particular reason why it couldn't work the way you expect, it just wasn't added to the language (yet).
As #wohlstad said, if constexpr is the solution:
if constexpr (cnt > 0)
{
if constexpr (std::same_as<std::remove_cvref_t<std::tuple_element_t<cnt - 1, decltype(tuple)>>, int>)
{
...
The first if must be constexpr, while the second only should (in your scenario).
You can force the compiler to evaluate the 2nd condition only if cnt > 0 using if constexpr (available since c++17) and separating into 2 nested ifs:
#include <cstdio>
#include <concepts>
#include <utility>
#include <tuple>
template <typename... Args>
auto foo(Args&&... args)
{
auto tuple = std::forward_as_tuple(std::forward<Args>(args)...);
constexpr std::size_t cnt = sizeof...(Args);
//-----vvvvvvvvv---------
if constexpr (cnt > 0)
{
if (std::same_as<std::remove_cvref_t<std::tuple_element_t<cnt - 1, decltype(tuple)>>, int>) {
printf("last is int\n");
}
else {
printf("last is not int\n");
}
}
}
int main()
{
foo(2);
foo();
}
Output:
last is int
Related
I'm trying to insert an enum parameter into a constexpr function. I have done this in the past and it always worked... except in this case. This case is only special in that I'm calling a factory function first. But apparently C++ doesn't see through this. What can be done?
Those are my errors:
<source>: In function 'constexpr auto operator+(some_enum)':
<source>:30:28: error: no matching function for call to 'signal<1, state>(std::nullptr_t)'
30 | return signal<1, state>(nullptr);
| ~~~~~~~~~~~~~~~~^~~~~~~~~
<source>:23:37: note: candidate: 'template<bool set, some_enum S, class ... Ts> constexpr signal_str<sizeof... (Ts)> signal(Ts ...)'
23 | constexpr signal_str<sizeof...(Ts)> signal(Ts... Args)
| ^~~~~~
<source>:23:37: note: template argument deduction/substitution failed:
<source>:30:28: error: 'state' is not a constant expression
30 | return signal<1, state>(nullptr);
| ~~~~~~~~~~~~~~~~^~~~~~~~~
<source>:30:28: note: in template argument for type 'some_enum'
<source>:28:16: error: invalid return type 'auto' of 'constexpr' function 'constexpr auto operator+(some_enum)'
28 | constexpr auto operator+(some_enum state)
| ^~~~~~~~
Compiler returned: 1
This is my code:
#include <array>
#include <cstdint>
#include <iostream>
typedef void* TaskType_t;
enum some_enum
{
SOME_STATE = 1,
};
template <size_t N>
struct signal_str
{
uint32_t val_;
std::array<TaskType_t, N> tasks_;
};
template <bool set, some_enum S, typename... Ts>
constexpr signal_str<sizeof...(Ts)> signal(Ts... Args)
{
return signal_str<sizeof...(Ts)>{S, {Args...}}.val_;
}
constexpr auto operator+(some_enum state)
{
return signal<1, state>(nullptr);
}
int main()
{
static_assert(+SOME_STATE);
}
I'm using C++17 on xtensa-gcc 8.2.0 but it's the same with gcc 11 (LIVE DEMO).
EDIT: This problem is different from "Why is const variable necessary for template specialization over constants" because enums are already constants. To showcase this the following DOES actually compile:
#include <array>
#include <cstdint>
#include <iostream>
typedef void* TaskType_t;
enum some_enum
{
SOME_STATE = 1,
};
template <size_t N>
struct signal_str
{
uint32_t val_;
std::array<TaskType_t, N> tasks_;
};
constexpr auto operator+(some_enum state)
{
return signal_str<1>{state, nullptr}.val_;
}
int main()
{
static_assert(+SOME_STATE);
}
DEMO
So the problem IMHO is not the enum..
When I try and get the index of a type within a list of types using below, the code compiles and returns the correct value when the else clause is used. However when I skip the else clause and place the return getIndex<T, Ts...>(x + 1); just after the end of the if clause the code fails to compile as it continues to unwind getIndex<T, Ts...> recursively resulting in the error shown below. This is the case with gcc and clang. Is this expected?
#include <type_traits>
template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
if constexpr(std::is_same_v<T,U>)
{
return x;
}
else
{
return getIndex<T, Ts...>(x + 1);
}
}
int main()
{
return getIndex<int, float, double, int, char>();
}
Error when moving return outside of else
getI.cc: In instantiation of ‘int getIndex(int) [with T = int; U = char; Ts = {}]’:
getI.cc:9:32: recursively required from ‘int getIndex(int) [with T = int; U = double; Ts = {int, char}]’
getI.cc:9:32: required from ‘int getIndex(int) [with T = int; U = float; Ts = {double, int, char}]’
getI.cc:14:51: required from here
getI.cc:9:32: error: no matching function for call to ‘getIndex<int>(int)’
9 | return getIndex<T, Ts...>(x + 1);
| ~~~~~~~~~~~~~~~~~~^~~~~~~
getI.cc:3:5: note: candidate: ‘template<class T, class U, class ... Ts> int getIndex(int)’
3 | int getIndex(int x = 0)
| ^~~~~~~~
getI.cc:3:5: note: template argument deduction/substitution failed:
getI.cc:9:32: note: couldn’t deduce template parameter ‘U’
9 | return getIndex<T, Ts...>(x + 1);
| ~~~~~~~~~~~~~~~~~~^~~~~~~
Peter Taran's answer has already touched on why the code compiles with the else clause, but he didn't really touch on why the compilation error occurs or how to fix it. Here is a working version of your code:
#include <type_traits>
template <typename T>
constexpr int getIndex(int x = 0)
{
return -1;
}
template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
if constexpr (std::is_same_v<T,U>)
{
return x + 1;// NOTE: you have a bug on this line
}
else
{
return getIndex<T, Ts...>(x + 1);
}
}
int main()
{
constexpr int index = getIndex<int, float, double, int, char>();
return 0;
}
Admittedly, I haven't used variadic templates much, but from what I am understanding here, the main problem is in this snippet of your compilation errors:
getI.cc:9:32: error: no matching function for call to ‘getIndex<int>(int)’
Despite having a base case defined in your recursive template, it seems like variadic templates are required to fully unpack. Because of that, you need a getIndex<T> defined, even if the code is not reached in your example. Adding the single argument getIndex<T> allowed the code to compile and run.
EDIT: This post is also related, a good read, and is also another possible solution to your issue.
As I realized, broken code is:
template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
if constexpr(std::is_same_v<T,U>) {
return x;
}
return getIndex<T, Ts...>(x + 1);
}
Accoring to this article:
In a constexpr if statement, the value of condition must be an
expression contextually converted to bool, where the conversion is a
constant expression. If the value is true, then statement-false is
discarded (if present), otherwise, statement-true is discarded.
Note that nothing is said about discarding statements outside if-else clause, so you should expect that recusive getIndex's will be instantiated.
In C++, I'm trying to write something similar to boost-mp11's mp_for_each. However, whilst mp_for_each always calls the supplied function for every T in the given mp_list<Ts...>, I'm trying to come up with a solution that stops traversal once a run-time call to the function yields a value evaluating to false in an if-statement.
See the implementation of mp_for_each and a usage example:
Implementation on GitHub
Usage example in Boost reference manual
Apparently, the implementation of mp_for_each manages to pass the function argument as a constant expression, thus enabling the user to apply it where a constant expression is required. Whilst I took a different approach incorporating template tail recursion, I expected the function argument to be passed as a constant expression as welll. However, GCC complains that it "is not a constant expression".
My code looks like this:
#include <cstdlib>
#include <iostream>
#include <typeinfo>
#include <utility>
#include <boost/mp11.hpp>
template<std::size_t T_counter>
struct WhileGreaterZero
{
template<typename T_Function>
constexpr WhileGreaterZero(T_Function&& function)
{
if (function(T_counter)) // pass function argument
WhileGreaterZero<T_counter - 1>(std::forward<T_Function>(function));
}
};
template<>
struct WhileGreaterZero<0>
{
template<typename T_Function>
constexpr WhileGreaterZero(T_Function&&) {}
};
int main()
{
using boost::mp11::mp_at_c;
using boost::mp11::mp_list;
using boost::mp11::mp_size;
using Types = mp_list<bool, int, double>;
WhileGreaterZero<mp_size<Types>::value - 1>(
[](auto counter) { // function parameter
using Type = mp_at_c<Types, counter>;
if (typeid(Type) == typeid(int))
return false;
return true;
}
);
}
When compiling with g++ 7.4.0, the following error is encountered (formatted to my taste):
$ g++ -std=c++17 -I/path/to/boost
wgz.cpp:
In substitution of ‘
template<
class L,
long unsigned int I
>
using mp_at_c =
typename boost::mp11::detail::mp_if_c_impl<
(I < typename boost::mp11::detail::mp_size_impl<L>::type:: value),
boost::mp11::detail::mp_at_c_impl<L, I>,
void
>::type::type
[
with L = boost::mp11::mp_list<bool, int, double>;
long unsigned int I = counter
]
’:
wgz.cpp:42:49:
required from ‘
main()::<lambda(auto:1)>
[with auto:1 = long unsigned int]
’
wgz.cpp:14:21:
required from ‘
constexpr WhileGreaterZero<T_counter>::WhileGreaterZero(T_Function&&)
[
with T_Function = main()::<lambda(auto:1)>;
long unsigned int T_counter = 2
]
’
wgz.cpp:49:5:
required from here
wgz.cpp:42:49:
error: ‘counter’ is not a constant expression
using Type = mp_at_c<Types, counter>;
^
wgz:42:49:
note: in template argument for type ‘long unsigned int’
Why is counter not considered as a constant expression in my code?
What's the crucial difference between mp11's code and mine in this regard?
Change
function(T_counter)
to
function(std::integral_constant< std::size_t, T_counter >{})
within function the argument is not a compile time value. But an integral_constant that isn't a compile time value can be cast to an integer, and that integer is a compile time constant, because it doesn't depend on this.
A related trick is:
template<std::size_t...Is>
constexpr auto indexes( std::index_sequence<Is...> ={} ) {
return std::make_tuple( std::integral_constant<std::size_t, Is>{}... );
}
then you can do:
template<std::size_t N, class F>
void While( F&& f ) {
std::apply( [&](auto...Is) {
(f( Is ) && ...);
}, indexes( std::make_index_sequence<N>{} ) );
}
Live example, no recursion.
Parameter of lambda is a parameter of function, its value is not passed at compile-time.
At very least this line is ill-formed:
using Type = mp_at_c<Types, counter>;
You have to wait for template lambdas or implement your own functor
I am trying to get the second element of the list, but I get an error:
||=== Build: Debug in hellocpp17 (compiler: GNU GCC Compiler) ===|
/home/idf/Documents/c++/hellocpp17/main.cpp||In function ‘int main()’:|
/home/idf/Documents/c++/hellocpp17/main.cpp|67|error: no matching function for call to ‘Get<2>::Get(std::__cxx11::list<unsigned int>&)’|
/home/idf/Documents/c++/hellocpp17/main.cpp|40|note: candidate: constexpr Get<2>::Get()|
/home/idf/Documents/c++/hellocpp17/main.cpp|40|note: candidate expects 0 arguments, 1 provided|
/home/idf/Documents/c++/hellocpp17/main.cpp|40|note: candidate: constexpr Get<2>::Get(const Get<2>&)|
/home/idf/Documents/c++/hellocpp17/main.cpp|40|note: no known conversion for argument 1 from ‘std::__cxx11::list<unsigned int>’ to ‘const Get<2>&’|
/home/idf/Documents/c++/hellocpp17/main.cpp|40|note: candidate: constexpr Get<2>::Get(Get<2>&&)|
/home/idf/Documents/c++/hellocpp17/main.cpp|40|note: no known conversion for argument 1 from ‘std::__cxx11::list<unsigned int>’ to ‘Get<2>&&’|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
Program:
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;
template<unsigned n>
struct Get
{
template<class X, class...Xs>
constexpr auto operator()(X x, Xs...xs)
{
if constexpr(n > sizeof...(xs) )
{
return;
}
else if constexpr(n > 0)
{
return Get<n-1> {}(xs...);
}
else
{
return x;
}
}
};
int main()
{
list<unsigned> l = { 7, 5, 16, 8 };
unsigned l2 = Get<2>(l);
cout << l2 << endl;
return 0;
}
EDIT 1
If I instantiate a Get<2>, this error is reported by the compiler
unsigned l2 = Get<2>()(l);
/home/idf/Documents/c++/hellocpp17/main.cpp|67|error: void value not ignored as it ought to be|
You can try with
unsigned l2 = Get<2>{}(7, 5, 16, 8);
The first problem in your code is that
Get<2>(l);
isn't a call to operator() of Get<2>; it's a construction of a Get<2> object with a std::list parameter.
Unfortunately there isn't a Get<2> constructor that receive a std::list.
The second problem in your code is that if you call the operator() of Get<2> as you think
Get<2>{}(l)
where l is a std::list, you pass a single argument; not a variadic list of arguments. And you can use the list l only run-time, not compile time as you want.
Unfortunately the Get<2>{}(7, 5, 16, 8) way (the operator() that receive a variadic list of arguments) is incompatible with a variable that contain a list.
I mean... you can't do something as follows
auto l = something{7, 5, 16, 8};
Get<2>{}(l);
But, if you modify the operator() to receive a std::integer_sequence as follows
template <template <typename X, X...> class C,
typename T, T I0, T ... Is>
constexpr auto operator() (C<T, I0, Is...> const &)
{
if constexpr (n > sizeof...(Is) )
return;
else if constexpr (n > 0)
return Get<n-1>{}(C<T, Is...>{});
else
return I0;
}
you can pass through a l variable as follows
auto l { std::integer_sequence<int, 7, 5, 16, 8>{} };
unsigned l2 = Get<2>{}(l);
I have written a generic exponential backoff retry loop in C++11. I'm using std::function to pass the callable to retry loop. callable will be retried if isRetriable function returns true.
#include <algorithm>
#include <cassert>
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>
constexpr int64_t max_backoff_milliseconds = 30000; // 30 seconds
template <class R, class... Args>
R Retry(int max_retry_count, int64_t initial_dealy_milliseconds,
const std::function<bool(R)> &isRetriable,
const std::function<R(Args...)> &callable, Args &&... args)
{
int retry_count = 0;
while (true) {
auto status = callable(std::forward<Args>(args)...);
if (!IsRetriable(status)) {
return status;
}
if (retry_count >= max_retry_count) {
// Return status and abort retry
return status;
}
int64_t delay_milliseconds = 0;
if (initial_dealy_milliseconds > 0) {
delay_milliseconds =
std::min(initial_dealy_milliseconds << retry_count,
max_backoff_milliseconds);
}
std::cout << "Callable execution failed. Retry Count:"
<< retry_count + 1 << std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(delay_milliseconds));
retry_count++;
}
}
bool isRetriable(int status) {
if (status == 5)
return true;
return false;
}
int foo(int x, int y) {
static int a = 1;
a += (x + y);
return a / 6;
}
int main() {
auto result = Retry(1000, 100, isRetriable, foo, 1, 3);
std::cout << result << std::endl;
return 0;
}
When I compile it, I'm getting below error:
prog.cpp: In function ‘int main()’:
prog.cpp:50:71: error: no matching function for call to ‘Retry(int,
int, bool (&)(int), int (&)(int, int), int, int)’
auto result = Retry<int, int, int>(1000, 100, isRetriable, foo, 1, 3);
^
prog.cpp:11:3: note: candidate: template<class R, class ... Args> R
Retry(int, int64_t, const std::function<bool(R)>&, const
std::function<_Res(_ArgTypes ...)>&, Args&& ...)
R Retry(int max_retry_count,
^~~~~
prog.cpp:11:3: note: template argument deduction/substitution failed:
prog.cpp:50:71: note: mismatched types ‘const
std::function<int(_ArgTypes ...)>’ and ‘int(int, int)’
auto result = Retry<int, int, int>(1000, 100, isRetriable, foo, 1, 3);
^
Could someone explain to me why I have this error?
I'm sure there's a good duplicate for this but...
Here's a shorter reproduction:
template <typename T> void foo(std::function<bool(T)> ) { }
bool maybe(int ) { return false; }
foo(maybe); // error: no matching function call to 'foo(bool (&)(int))'
You may ask - what?! maybe is something that's callable with some T that returns bool. But that's not how template deduction works. In order to deduce std::function<bool(T)> against an argument, that argument needs to be a std::function. maybe isn't a std::function, it's just a function, so that deduction fails. Any kind of deduction with a different kind of expression will also fail:
foo([](int ) { return true; }); // also error
Basically, trying to deduce a std::function is almost always the wrong thing to do. First, it's wrong because it won't work. Second, it's wrong because even if it did work, you're incurring type erasure in a context in which you probably don't need it.
What you want to do instead is deduce arbitrary callables, and then determine what these other arguments are based on those callables. The return type of callable is just what you get when you call callable with Args..., and you want to ensure that isRetriable is a predicate on that type.
One approach to that is:
template <typename Predicate, typename Callable, typename... Args,
// figure out what the callable returns
typename R = std::decay_t<std::invoke_result_t<Callable&, Args...>>,
// require that Predicate is actually a Predicate
std::enable_if_t<
std::is_convertible_v<std::invoke_result_t<Predicate&, R>, bool>,
int> = 0>
R Retry(int max_retry_count, int64_t initial_dealy_milliseconds,
Predicate&& isRetriable,
Callable&& callable,
Args&&... args)
{
// ....
}