C++ Lambdas with Ellipses in the Parameter List - c++

I'm working on a library which uses lambdas for delineating the scopes of expression terms. Because the library has to hand out unique integer numbers to identify each variable, it is ideal if the library, not the user, constructs the variables and the user code receives them as lambda arguments.
(In other words I am implementing a C++ analog of "call\fresh" from miniKanren.)
Since the user may want to introduce any number from zero to many fresh variables at a particular scope, I want the user to be able to pass lambdas with differing numbers of arguments to the library. However, I'm not aware of any (simple) way (in C++14) to deduce the number of parameters to an arbitrary lambda object.
An idea occurred to me why not pass a fixed number (say, 10) of variable-id arguments to the lambda, and have the user code use ellipses in the lambda to ignore the ones not needed? Something like this:
auto no_args = call_fresh([](...) { return success(); });
auto one_arg = call_fresh([](var A, ...) { return A == 1; });
auto two_args = call_fresh([](var A, var B, ...) { return A == 1 && B == 2; });
Compiler explorer seems to accept ellipses in lambda parameter lists, at least with gcc.
It would be called something like this (note how the code always passes 10 variable id's no matter whether "f" names only one, two, or none of them):
template <typename F>
auto call_fresh(F f)
{
return [f](StateCounter sc) {
return f(sc+0,sc+1,sc+2,sc+3,sc+4,
sc+5,sc+6,sc+7,sc+8,sc+9);
};
}
Granted it's a feature I was surprised exists, is there any reason not to use lambdas with ellipses?

However, I'm not aware of any (simple) way (in C++14) to deduce the number of parameters to an arbitrary lambda object.
It seems to me that you're looking for sizeof...() over a variadic auto list of paramenters
#include <iostream>
int main ()
{
auto l = [](auto ... as) { return sizeof...(as); };
std::cout << l(1, 2L, 3.0, 4.0f, "5") << std::endl; // print 5
}

Your lambdas are essentially C-style variadic functions. There's nothing wrong with using them, and if you don't want to access the values (which is somewhat ugly), that is fine.
However, the underlying problem that it seems like you actually want to solve is to let your library find the number of arguments (or arity) of a function/lambda/..., which you can do with template metaprogramming - no need for your users to work around that issue.
Disclosure: There is an implementation of this in a library that I also work on, here.
Here is a simple example:
template <typename Callable>
struct function_arity : public function_arity<decltype(&Callable::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_arity<ReturnType(ClassType::*)(Args...) const>
{
constexpr static size_t arity = sizeof...(Args);
};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_arity<ReturnType(ClassType::*)(Args...)>
{
constexpr static size_t arity = sizeof...(Args);
};
The compiler will automatically deduce the argument types for you, and sizeof... will get you the number of arguments that you need.
Then, you can use function_arity<decltype(lambda)>::arity to get the number of arguments of your lambda. The last version deals with mutable lambdas, where the call operator is non-constant. You may also want to extend this to work properly with noexcept, or you will run into errors like this libc++ bug.
Unfortunately, this will not work with overloaded or templated operator() (e.g. if you use auto-type parameters in your lambda). If you also want to support functions instead of lambdas, additional specializations may be necessary.

Related

Concept for constraining parameter pack to string-like types or types convertible to string

I'm piecing together a C++20 puzzle. Here's what I want to do: Function append_params will concatenate the url together with additional query parameters. To make design this in a dynamic and extensible way, I wanted to write a concept such that
it allows types that an std::string can be constructed from
it allows types convertible to string using std::to_string()
template<typename... Ts> requires requires(T a) { std::to_string(a); }
auto append_params(std::pmr::string url, Ts... args) {
}
it works for a pack of parameters
I've found useful information on point 2) here. However, for point 1) and 3) I'm rather clueless (I'm also new to concepts). How can I constrain the whole parameter pack (what's the syntax here?) and how can I make sure that from every parameter I can construct an std::string object?
Also, I would have to know at compile time if I want to use std::strings constructor or std::to_string to handle the case.
When developing a concept, always start with the template code you want to constrain. You may adjust that code at some point, but you always want to start with that code (unconstrained).
So what you want is something like this:
template<typename... Ts>
auto append_params(std::string &url, Ts... &&args)
{
return (url + ... + std::forward<Ts>(args) );
}
This doesn't work if any of the types in args are not std::strings or something that can be concatenated with std::string. But you want more. You want to take types which std::to_string can be used on.
Note that this is a pretty bad interface, since std::to_string cannot be extended by the user. They can make their types convertible to std::string or some other concatenate-able string type like string_view. But users cannot add overloads to std library stuff, so they can't make a type to_stringable.
Regardless, to allow to_string to work, we would need to change the code to concatenate with this expression: std::to_string(std::forward<Ts>(args)). But that would only take types that to_string works on, not types convertible to a string.
So what we need is a new function which will use to_string if that is appropriate or just return the original expression if it can be concatenated directly.
So we have two kinds of types: those that are inherently string-concatenatable, and those which are to_string-able:
template<typename T>
concept is_direct_string_concatenatable = requires(std::string str, T t)
{
{str + t} -> std::same_as<std::string>;
};
template<typename T>
concept is_to_stringable = requires(T t)
{
{std::to_string(t)} -> std::same_as<std::string>;
};
//Combines the two concepts.
template<typename T>
concept is_string_concatenable =
is_direct_string_concatenatable<T> ||
is_to_stringable <T>;
template<is_string_concatenable T>
decltype(auto) make_concatenable(T &&t)
{
//To_string gets priority.
if constexpr(is_to_stringable<T>)
return std::to_string(std::forward<T>(t));
else
return std::forward<T>(t);
}
So now your template function needs to use make_concatenable and the concept:
template<is_string_concatenable ...Ts>
auto append_params(std::string url, Ts&& ...args)
{
return (url + ... + make_concatenable(std::forward<Ts>(args)));
}

If I create a constexpr function to only generate a compile time constant, shouldn't there be a way to generate a constexpr value from the parameters?

I understand that constexpr function return values cannot be compile time constants until the function has returned. Thus this works:
template <typename...Ts>
constexpr auto f(Ts&&...args) {
auto value = std::tuple<Ts...>(args...);
return some_constexpr_transform_function(value);
}
constexpr auto vf = f(1, 2.3, 4);
However, for the case where the function's parameters are only compile time constants, and thus the function is capable of returning a compile time constant, one should be able to take those arguments and generate a compile time constant to do some compile time magic on it.
template <typename...Ts>
constexpr auto g(Ts&&...args) {
constexpr auto value = std::tuple<Ts...>(args...);
// do some compile time magic here on value, like:
static_assert(std::get<0>(value) == 1, "The first parameter must be 1.");
return some_constexpr_transform_function(value);
}
constexpr auto vg = g(1, 2.3, 4);
This is with the understanding that this function is no longer callable with runtime parameters. Alas this isn't the case. Instead I have to do some ugly indirection stuff, requiring things to look messy and putting initialization out of place:
namespace detail {
// Sorry future dev, I know that this init is better off below, but
// I have to make this look ugly to do compile time validation tests.
constexpr auto ugly_intermediate_constexpr_value = std::make_tuple(1, 2.3, 4);
static_assert(std::get<0>(ugly_intermediate_constexpr_value) == 1
, "The first parameter must be 1.");
}
template <typename...Ts>
constexpr auto h(std::tuple<Ts...>&& args) {
return some_constexpr_transform_function(args);
}
constexpr auto vh = h(detail::ugly_intermediate_constexpr_value);
Is there any talk about fixing this? This seems to be a pretty big deficiency.
NOTE: I'm currently using c++14, and I know that I didn't use the perfect forwarding idiom. It's irrelevant to the question.
The biggest problem with making parameters constexpr, one which even P1045 has trouble dealing with, has to do with things users want most to be able to do when doing constant expression coding. Consider the following code:
template<int v>
constexpr auto func()
{
if constexpr(v == 0)
return int{5};
else
return float{20.f};
}
So, what is decltype(func)? The answer is that there is no answer because the question is invalid. func is not a function; it is a template. Templates have no "type", so decltype makes no sense when applied to them.
Templates are constructs which generate new types/functions/variables, based on their template arguments. The above works in C++ because func<0> is a different function from func<1>. Therefore, decltype(func<0>) is a legitimate thing and can be a different type from decltype(func<1>).
Consider a hypothetical constexpr equivalent:
constexpr auto func2(constexpr int v)
{
if constexpr(v == 0)
return int{5};
else
return float{20.f};
}
OK: what is decltype(func2)? There can be no answer, because what func2 does depends on the arguments it is called with. In effect, func2 is not a function: it is really some kind of construct which generates a function based on arguments it is given. And C++ already has such a language construct.
It's called a "template".
This problem relates to more than just function return types (though that's a big one for metaprogramming and reflection). Consider something as simple as:
constexpr void func3(constexpr size_t sz)
{
std::array<int, sz> arr{};
//Other stuff.
}
The compiler needs to generate the code to value-initialize arr. But that will have to have a size that depends on sz. Indeed, the amount of stack space that is taken up depends on sz. So... how does that work?
Each function call with a different sz value effectively has to regenerate the internals of the function... in exactly the same way as a template would for a different template argument value.
Basically, any attempt to make constexpr parameters is going to confront the reality that these are just template parameters. It's going to have to build a mechanism that exactly parallels template instantiation.
And with C++20 already having class types as non-type template parameters, the only thing that's wanting is a bit of syntactic sugar, allowing you to invoke func(0) rather than func<0>().
So just use template parameters.

Compile time sort of heterogenous tuples

I know it's possible to use the C++ type system to generate a sorted type list from an existing tuple type.
Examples of doing this can be found at:
https://codereview.stackexchange.com/questions/131194/selection-sorting-a-type-list-compile-time
How to order types at compile-time?
However, is it possible to do a compile-time sort of a heterogeneous tuple by value? For example:
constexpr std::tuple<long, int, float> t(2,1,3);
constexpr std::tuple<int, long, float> t2 = tuple_sort(t);
assert(t2 == std::tuple<int, long, float>(1,2,3));
My assumption is this is not possible, since you'd have to conditionally generate new tuple types based on the result of comparing values. Even if the comparison function uses constexpr, it would seem that this can't work.
However, an offhand comment from this answer indicates it somehow is possible to do this, just very difficult:
I lied. You can do it if the values and the compare function are
constexpr, but the code to pull it off will be huge and not worth the
time to write.
So is this comment correct? How could this even be conceptually possible, given the way the C++ type system works.
To preface the answer, it might be vastly more straightforward to use Boost.Hana. The prerequisite for Hana is that your comparison produces a compile-time answer. In your case, this would require a Hana tuple containing compile-time versions of these basic data types, similar to std::integral_constant. If it's acceptable to have your tuples' values encoded entirely in their types, Hana makes this trivial.
I believe it would be possible to do this directly once you can use a tuple as a non-type template parameter in C++20. Until then, you can get pretty close (live example):
int main() {
constexpr std::tuple<long, int, float> t(2,1,3);
call_with_sorted_tuple(t, [](const auto& sorted) {
assert((sorted == std::tuple<int, long, float>(1,2,3)));
});
}
As far as I know, it is impossible to return the sorted tuple directly; the callback approach is required because it is instantiated with every possible tuple type and only the correct one is actually run. This means there is significant compile-time overhead to this approach. The compile times grow quickly with small tuple size increases.
Now, how does this actually work? Let's get the magic out of the way—converting a runtime integral value into a compile-time one. This can fit well into its own header, and is shamelessly stolen from P0376:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0376r0.html
#include <array>
#include <type_traits>
#include <utility>
// A function that invokes the provided function with
// a std::integral_constant of the specified value and offset.
template <class ReturnType, class T, T Value, T Offset, class Fun>
constexpr ReturnType invoke_with_constant_impl(Fun&& fun) {
return std::forward<Fun>(fun)(
std::integral_constant<T, Value + Offset>());
}
// Indexes into a constexpr table of function pointers
template <template <class...> class ReturnTypeDeducer,
class T, T Offset, class Fun, class I, I... Indices>
constexpr decltype(auto) invoke_with_constant(Fun&& fun, T index,
std::integer_sequence<I, Indices...>) {
// Each invocation may potentially have a different return type, so we
// need to use the ReturnTypeDeducer to figure out what we should
// actually return.
using return_type
= ReturnTypeDeducer<
decltype(std::declval<Fun>()(std::integral_constant<T, Indices + Offset>()))...>;
return std::array<return_type(*)(Fun&&), sizeof...(Indices)>{
{{invoke_with_constant_impl<return_type, T, Indices, Offset, Fun>}...}}
[index - Offset](std::forward<Fun>(fun));
}
template <class T, T BeginValue, T EndValue>
struct to_constant_in_range_impl {
// Instantiations of "type" are used as the Provider
// template argument of argument_provider.
template <class U>
struct type
{
template <template <class...> class ReturnTypeDeducer, class Fun, class Self>
static constexpr decltype(auto) provide(Fun&& fun, Self&& self) {
return invoke_with_constant<ReturnTypeDeducer, T, BeginValue>(
std::forward<Fun>(fun),
std::forward<Self>(self).value,
std::make_index_sequence<EndValue - BeginValue>());
}
U&& value;
};
};
Now one thing to note is that I use C++20's ability to give lambdas template parameters simply because compilers already support this and it makes turning index_sequences into parameter packs really easy. The long way to do this is available prior to C++20, but a bit of an eyesore on top of code that's already hard enough to get through.
The sort itself isn't too bad despite the tuple needing compile-time indices for std::get (unless you reuse the above magic, but all I have to say to that is yikes). You can change the algorithm as needed. You can even use a regular std::vector in C++20 and push indices onto the back. What I chose to do is generate a std::array containing the sorted-order indices of the tuple:
// I had trouble with constexpr std::swap library support on compilers.
template<typename T>
constexpr void constexpr_swap(T& a, T& b) {
auto temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}
template<std::size_t I>
using index_c = std::integral_constant<std::size_t, I>;
template<typename... Ts>
constexpr auto get_index_order(const std::tuple<Ts...> tup) {
return [&]<std::size_t... Is>(std::index_sequence<Is...> is) {
std::array<std::size_t, sizeof...(Is)> indices{Is...};
auto do_swap = [&]<std::size_t I, std::size_t J>(index_c<I>, index_c<J>) {
if (J <= I) return;
if (std::get<I>(tup) < std::get<J>(tup)) return;
constexpr_swap(indices[I], indices[J]);
};
auto swap_with_min = [&]<std::size_t I, std::size_t... Js>(index_c<I> i, std::index_sequence<Js...>) {
(do_swap(i, index_c<Js>{}), ...);
};
(swap_with_min(index_c<Is>{}, is), ...);
return indices;
}(std::index_sequence_for<Ts...>{});
}
The main idea here is obtaining a pack of indices from 0 to N-1 and then dealing with each individually. Rather than trying to generate a second pack from I+1 to N-1, I took the easy road and reused the 0 to N-1 pack I already had, ignoring all out-of-order combinations when swapping. The dance with index_c is to avoid calling the lambdas via awkward lambda.template operator()<...>(...) syntax.
Now we have the indices of the tuple in sorted order and magic to convert one index to one with its value encoded in the type. Rather than build the magic to handle multiple values, I took the probably-suboptimal approach to build on the support for one at a time by making a recursive function:
template<typename... Ts, typename F, std::size_t... Converted>
constexpr void convert_or_call(const std::tuple<Ts...> tup, F f, const std::array<std::size_t, sizeof...(Ts)>& index_order, std::index_sequence<Converted...>) {
using Range = typename to_constant_in_range_impl<std::size_t, 0, sizeof...(Ts)>::template type<const std::size_t&>;
if constexpr (sizeof...(Converted) == sizeof...(Ts)) {
f(std::tuple{std::get<Converted>(tup)...});
} else {
Range r{index_order[sizeof...(Converted)]};
r.template provide<std::void_t>([&]<std::size_t Next>(index_c<Next>) {
convert_or_call(tup, f, index_order, std::index_sequence<Converted..., Next>{});
}, r);
}
}
I would have made this a lambda to avoid repeating captures, but as its recursive, it needs a workaround to call itself in lambda form. I'd be happy to hear of a good, constexpr-compatible solution for lambdas in this case that takes into account the fact that the lambda's template arguments differ each call.
Anyway, this is the use of the magic. We want to call this a total of N times, where N is the tuple size. That's what the if constexpr checks for, and finally delegates to the function passed from main, easily building a new tuple from the compile-time index order sequence. To recurse, we add on this compile-time index to a list we build up.
Finally, since what should have been a lambda is its own function, the function called from main is a simple wrapper that gets the index-order array and starts off the runtime-sequence-to-compile-time-sequence recursion with nothing converted to start:
template<typename... Ts, typename F>
constexpr void call_with_sorted_tuple(const std::tuple<Ts...>& tup, F f) {
auto index_order = get_index_order(tup);
convert_or_call(tup, f, index_order, std::index_sequence<>{});
}
I believe, it can not be done.
The essential part of any sorting would be to use tuple value in if constexpr context, but since function arguments are not constexpr, they can not appear in if constexpr.
And since tuples can't be non-type template arguments, template-based solution can't be implemented either. Unless we make tuple of type-encoded values (like std::integral_constant) I believe the solution is not available.
Return type cannot depend of value of parameters (even more as parameter cannot be constexpr) of a function, so
constexpr std::tuple<long, int, float> t1(2, 1, 3);
constexpr std::tuple<long, int, float> t2(3, 2, 1);
static_assert(std::is_same<decltype(tuple_sort(t1), decltype(tuple_sort(t2)>::value, "!");

Limiting the number of arguments in a variadic function

So I've been working on a function class, and by default, I can do this, and it works:
int main(){
function f("x^2+1");
cout<<f(3)<<endl;
return 0;
}
"assume proper includes and namespaces"
Anyways, I would like to be able to pass in multiple variables, and even state what those variables are, like;
function f("x^2+y^2",x,y); // it doesn't really matter if it's x, 'x', or "x"
cout<<f(3,4)<<endl; // input 3 as x, and 4 as y
I'm fairly certain I could figure something out for the constructor using variadic functions, and even solve properly, but would there be a way to force the operator() arguments to take in exactly 2 values?
I was just looking at variadic functions because they are really the first things I've seen in c++ that can take in multiple arguments, so if it is better to do this some other way, I'm all for it.
You can limit the number of variadic arguments by using a static_assert.
template <typename ... Args>
void operator()(Args&&... args)
{
static_assert(sizeof...(Args) <= 2, "Can deal with at most 2 arguments!");
}
Or you could use an enable_if
template <typename ... Args>
auto operator()(Args&&... args) -> std::enable_if_t<sizeof...(Args) <= 2>
{
}
template<class T>
using double_t=double;
template<class...Ts>
using nfun=std::function<double(double_t<Ts>...)>;
template<class...C>
nfun<C...> func(const char*,C...c);
that will return a n-ary std::function equal to the number of 'variable' arguments to func.
So func("x^2+y",'x','y','z') will return std::function<double(double,double,double)> as an example.

Order of function definition during template instantiation

I have trouble understanding the order of template instantiation. It seems that the compiler does not consider a function if it is defined "too late." The following steps illustrates the main ideas of the code below:
The framework should provide a free function convert<From, To> if it can find a working overload for the function generate.
The function to<T> is a shortcut for convert<From,To> and should only work if convert<From,To> is valid.
Users should be able to provide an overload of generate and be able to use to and convert.
The corresponding code:
#include <string>
#include <utility>
#include <iostream>
// If I move the code down below at [*] to this location, everything works as
// expected.
// ------------- Framework Code -------------
// Anything that can be generated can also be converted to a string.
template <typename From>
auto convert(From const& from, std::string& to)
-> decltype(
generate(std::declval<std::back_insert_iterator<std::string>&>(), from)
)
{
to.clear();
auto i = std::back_inserter(to);
return generate(i, from);
}
// Similar to convert, except that it directly returns the requested type.
template <typename To, typename From>
auto to(From const& f) -> decltype(convert(f, std::declval<To&>()), To())
{
To t;
if (! convert(f, t))
throw std::invalid_argument("invalid conversion");
return t;
}
// ------------- User Code -------------
// [*] Support arithmetic types.
template <typename Iterator, typename T>
auto generate(Iterator& out, T i)
-> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type
{
// Note: I merely use std::to_string for illustration purposes here.
auto str = std::to_string(i);
out = std::copy(str.begin(), str.end(), out);
return true;
}
int main()
{
uint16_t s = 16;
std::cout << to<std::string>(s) << std::endl;
return 0;
}
The problem in the following code is that it only works if the function generate appears before the definition of convert and to. How can I work around this problem?
Maybe my mental model is wrong here, but I thought the template when the compiler sees to<std::string>(uint16_t), it starts going backwards to and instantiate as needed. Any guidance would be appreciated.
The compiler does not know of the existence of generate by the time it sees the definition of convert and to, as you have already guessed yourself. Contrary to what you thought, it doest not though sort of put the definitions of convert and to "on hold" until it sees what generate is. To workaround this problem you need to forward declare generate, what can be done using the following construction:
template <typename Iterator, typename T>
auto generate(Iterator& out, T i)
-> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type;
This should appear right before the definition of convert, so that the compiler knows generate actually exists and also is a function by the time it compiles convert and to. This way the compiler can check the syntax and guarantee it is a valid call to generate, even before it knows what generate actually does, since all it needs to do at this point is check if the types of the arguments as well as of the return value match, according to the rules defined by the language standard.
By doing this, you naturally enforce a specific type signature for generate (remember the compiler is required to check the types when it compiles convert and to!). If you don't want to do that, and you probably don't, then the best approach is to expect a further template argument to convert and to likewise, which you expect to be callable, that is, which you can use as in a function call:
template <typename From, typename Generator>
auto convert(From const& from, std::string& to, Generator generate)
-> decltype(
generate(std::declval<std::back_insert_iterator<std::string>&>(), from)
)
{
to.clear();
auto i = std::back_inserter(to);
return generate(i, from);
}
These kind of objects are commonly known as callable objects.
The drawback to this approach is that because c++ unfortunately does not support concepts yet, you can't do much to enforce the requirements the callable object generate should attend to. Nonetheless, this approach is what the std library successfully uses for its algorithms.
The advantage of this approach is that it can be very flexible, for any possible callable object, which minimally attends to the type requirements, can be used. That includes free functions, function objects, member functions through binding, among others. Not to mention the user is absolutely free to choose the name she wants for her callable object instead of being forced to use generate, as your initial idea would require if it were valid c++.
Now to call this modified version of convert using the free function generateyou defined, you would do that:
to<std::string>(s, generate<std::back_insert_iterator<std::string>, uint16_t>);
That isn't very nice, because since you must explicitly state the template arguments, this approach fails to take full advantage of the fact generate is a template function. Fortunately this inconvenience can be overcome by the use of function objects, like the following for instance:
struct Generator
{
template <typename Iterator, typename T>
auto operator()(Iterator& out, T i)
-> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type
{
// Note: I merely use std::to_string for illustration purposes here.
auto str = std::to_string(i);
out = std::copy(str.begin(), str.end(), out);
return true;
}
};
The previous call would become simply
to<std::string>(s, Generator());
taking full advantage of its tamplate nature.
At any rate, if I got the idea correctly, this part of the code is of responsability of the user, so she, as she deserves, have full autonomy to decide which way she prefers.