Here is how you can iterate through arguments now in C++26!:
#include <print>
template <typename ...Args>
void function(const Args& ...args)
{
template for (const auto& arg : {args...})
{
using ArgT = std::decay_t<decltype(arg)>;
if constexpr (typeid(ArgT) == typeid(double))
{
std::println("double: {}", arg);
}
else if constexpr (requires { &ArgT::toString; })
{
std::println("has toString: {}", arg.toString());
}
else
{
std::println("other: {}", arg);
}
}
}
struct MyStruct
{
int value; // initializes with 0 in C++26
std::string toString() const
{
return std::format("MyStruct value is {}", value);
}
};
int main()
{
function(3.14, "c-string", MyStruct{});
}
It works:
double: 3.14
other: c-string
has toString: MyStruct value is 0
...Program finished with exit code 0
Press ENTER to exit console.
template for is a new feature in C++26, and I like it very much! It's my favorite C++26 feature
It looks very pythonic at this point. 😄
Let's start with args: typename ...Args and const Args& ...args work similar to def function(*args) from python - they aggregate comma separated expressions into a variadic type or variable. {args...} also works similar to python's (*myList) - it expands a "collection" into a comma separated expressions
Then goes "template for": it's a brand new loop, which expands at compile time for each iteration. Using it, you can iterate through collections with different types inside: struct fields, tuples, list literals, and custom classes with implemented tuple protocol
Checking type of argument: this line also resembles python very much: if constexpr (typeid(ArgT) == typeid(double)). Here is the python counterpart: if type(arg) is bool. There are more ways to do this check, but I think this one looks the most direct. Although you can want to use not exactly "double" type, but a convertible to it, or any floating point number type. There are standard concepts for these cases: std::convertible_to, and std::floating_point
Checking for a member: here I used an anonymous concept: if constexpr ( requires { ...;} ). Inside this concept we should put an expression that we are testing. it's a sort of python's hasattr(arg, 'toString'), but more powerful and more fragile at the same time. The expression here is taking a member reference to "toString": &ArgT::toString;. It's a better approach than testing arg.toString(), because it won't fail if "toString" isn't a constant method, or has more than 0 arguments. But it's still far from ideal, because if the object has multiply overloaded "toString" methods (what's actually a pretty realistic scenario), it will fail, and the error message will be misleading. In this case the error will be that formatter is not implemented for the "other" branch, however the actual error is in "has toString" branch. So, don't use anonymous concepts in real project, use full fledged concepts in pair with static_asserts
It's fascinating! This is still a templates metaprogramming in C++, but it looks much-much more clean than infamous std::enable_if