How to remove redundancy from and add flexibility to template parameters? - c++

I want to use constexpr, compile time generated std::arrays for fast value-lookup instead of lengthy runtime calculations. For that I drafted a templated constexprfunction that will be executed at compile time.
Please see the following example code, which allows for ultrafast access to Triangle and Fibonacci numbers and factorials.
#include <iostream>
#include <utility>
#include <array>
constexpr size_t ArraySize = 20u;
// Some generator functions -------------------------------------------------------------
constexpr size_t getTriangleNumber(size_t row) noexcept {
size_t sum{};
for (size_t i{ 1u }; i <= row; i++) sum += i;
return sum;
}
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
return f2;
}
constexpr unsigned long long getFactorial(size_t index) noexcept {
unsigned long long result{ 1 };
while (index > 0) { result *= index; --index; }
return result;
}
// Generate a std::array with n elements of a given type and a generator function -------
template <typename DataType, DataType(*generator)(size_t), size_t... ManyIndices>
constexpr auto generateArray(std::integer_sequence<size_t, ManyIndices...>) noexcept {
return std::array<DataType, sizeof...(ManyIndices)>{ { generator(ManyIndices)... } };
}
// The arrays ---------------------------------------------------------------------------
constexpr auto TriangleNumber = generateArray<size_t, getTriangleNumber>(std::make_integer_sequence<size_t, ArraySize>());
constexpr auto FibonacciNumber = generateArray<unsigned long long, getFibonacciNumber>(std::make_integer_sequence<size_t, ArraySize>());
constexpr auto Factorial = generateArray<unsigned long long, getFactorial>(std::make_integer_sequence<size_t, ArraySize>());
// Some debug test driver code
int main() {
for (const auto t : TriangleNumber) std::cout << t << ' '; std::cout << '\n';
for (const auto f : FibonacciNumber) std::cout << f << ' '; std::cout << '\n';
for (const auto f : Factorial) std::cout << f << ' '; std::cout << '\n';
return 0;
}
As you can see. The template uses a parameter "DataType". In my opinion this is redundant. This is always the return type of the generator function. And it will also determine the data type for the std::array
So, how can we eliminate this redundancy and just use the type given by the generator function?
Additionally. The functions parameter is always size_t. There is also a redundancy and it is also not very flexible. The type of "ManyIndices" and the function parameter are always the same. So, no need to write that double.
Regarding flexibility. If I want to use a generator function with a different parameter data type, say, unsigned long long as in
constexpr unsigned long long factorial(unsigned long long n) noexcept {
return n == 0ull ? 1ull : n * factorial(n - 1ull);
}
I cannot do that. So, basically everything should be deduced from the generators functions signature.
This is also valid for the lines like
constexpr auto Factorial = generateArray<unsigned long long, getFactorial>(std::make_integer_sequence<size_t, ArraySize>());
Here, size_t is also the type of the parameter of the given function.
So, how eliminate redundancy and add flexibility?

DataType can be deduced from passed generator, use std::declval.
std::integer_sequence can be replaced by std::index_sequence.
Size for calculation must be provided explicitly.
template <typename GEN, size_t ... Indices>
constexpr auto generateArray2Helper(GEN gen, std::index_sequence<Indices...>) {
return std::array<decltype(std::declval<GEN>()(size_t{})), sizeof...(Indices)>{ gen(Indices)... };
}
template <size_t N, typename GEN>
constexpr auto generateArray2(GEN gen) {
return generateArray2Helper(gen, std::make_index_sequence<N>());
}
// The arrays ---------------------------------------------------------------------------
constexpr auto TriangleNumber = generateArray2<ArraySize>(getTriangleNumber);
constexpr auto FibonacciNumber = generateArray2<ArraySize>(getFibonacciNumber);
constexpr auto Factorial = generateArray2<ArraySize>(getFactorial);
Demo

c++20 version:
template<std::size_t...Is>
constexpr auto index_over(auto f, std::index_sequence<Is...>){
return f(std::integral_constant<std::size_t,Is>{}...);
}
template<auto N>
constexpr auto index_upto(auto f){
return index_over(f, std::make_index_sequence<N>{});
}
template<auto size>
constexpr auto gen_array(auto f){
return index_upto<size>([&](auto...Is){
return std::array{f(Is)...};
});
}

Related

passing pointer to const int in recursive call

How to pass a pointer to const int in a recursive call. I am using the following code format to calculate Fibonacci recursively, but am getting the error:
error: lvalue required as unary '&' operand**
#include <iostream>
void fun(const int *n)
{
fun( &(*n-1) ); // it is giving error.
}
int main()
{
const int n = 4;
fun(&n);
}
You would have to use another variable then, to which you assign the decremented const variable: you simply can't pass a decreased value of a const variable, since by definition, it is not modifiable neither by increment or decrement.
#include <iostream>
void fun2 (const int *n)
{
std::cout << *n << std::endl;
}
void fun1(const int *n)
{
int x = *n-1;
fun2( &x );
}
int main()
{
const int n = 4;
fun1(&n);
}
as #nastor pointed out in comment using local variable problem solved.
#include <iostream>
void fun2 (const int *n)
{
std::cout << *n << std::endl;
}
void fun1(const int *n)
{
int x = *n-1;
fun2( &x );
}
int main()
{
const int n = 4;
fun1(&n);
}
void fun(const int *n)
{
fun( &(*n-1) ); // it is giving error.
}
You already passed a pointer, so you don't need to use the & as this would create a pointer from the pointer. Just pass the pointer. If you don't want to modifiy the parent value, then you don't need a pointer.
void fun(int n)
{
if (!n)
return;
fun(n-1);
}
int main()
{
const int n = 4;
fun(n);
}
All answers are already given and correct.
Unrelated to the level of the question, I will add the fastest possible solution for those who are interested in high speed Fibonacci number retrieval.
We simply do a compile time pre calculation of all Fibonacci numbers that fit into a 64 bit value. And then use a simple look up mechanism to get the value.
One important property of the Fibonacci series is that the values grow strongly exponential. So, all existing build in integer data types will overflow rather quickly.
With Binet's formula you can calculate that the 93rd Fibonacci number is the last that will fit in a 64bit unsigned value.
And calculating 93 values during compilation is a really simple task and does not waste that much space in the executable.
We will first define the default approach for calculation a Fibonacci number as a constexpr function:
// Constexpr function to calculate the nth Fibonacci number
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
return f2;
}
With that, Fibonacci numbers can easily be calculated at compile time. Then, we fill a std::array with all Fibonacci numbers. We use also a constexpr function and make it a template with a variadic parameter pack.
We use std::index_sequence to create a Fibonacci number for indices 0,1,2,3,4,5, ....
That is straigtforward and not complicated:
// Some helper to create a constexpr std::array initilized by a generator function
template <typename Generator, size_t ... Indices>
constexpr auto generateArrayHelper(Generator generator, std::index_sequence<Indices...>) {
return std::array<decltype(std::declval<Generator>()(size_t{})), sizeof...(Indices) > { generator(Indices)... };
}
This function will be fed with an index sequence 0,1,2,3,4,... and a generator function and return a std::array<return type of generator function, ...> with the corresponding numbers, calculated by the generator.
We know that we can store maximum 93 values. And therefore we make a next function, that will call the above with the index sequence 1,2,3,4,...,92,93, like so:
template <size_t Size, typename Generator>
constexpr auto generateArray(Generator generator) {
return generateArrayHelper(generator, std::make_index_sequence<Size>());
}
And now, finally,
constexpr auto FibonacciNumber = generateArray<MaxArraySize64BitFibonacci>(getFibonacciNumber);
will give us a compile-time std::array<unsigned long long, 93> with the name FibonacciNumber containing all Fibonacci numbers. And if we need the i'th Fibonacci number, then we can simply write FibonacciNumber [i]. There will be no calculation at runtime.
I do not think that there is a faster way to calculate the n'th Fibonacci number.
Please see the complete program below:
#include <iostream>
#include <utility>
#include <array>
// All done during compile time -------------------------------------------------------------------
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
return f2;
}
// Some helper to create a constexpr std::array initilized by a generator function
template <typename Generator, size_t ... Indices>
constexpr auto generateArrayHelper(Generator generator, std::index_sequence<Indices...>) {
return std::array<decltype(std::declval<Generator>()(size_t{})), sizeof...(Indices) > { generator(Indices)... };
}
template <size_t Size, typename Generator>
constexpr auto generateArray(Generator generator) {
return generateArrayHelper(generator, std::make_index_sequence<Size>());
}
constexpr size_t MaxArraySize64BitFibonacci = 93;
// This is the definition of a std::array<unsigned long long, 93> with all Fibonacci numbers in it
constexpr auto FibonacciNumber = generateArray<MaxArraySize64BitFibonacci>(getFibonacciNumber);
// End of: All done during compile time -----------------------------------------------------------
// Some debug test driver code
int main() {
for (const auto f : FibonacciNumber) std::cout << f << ' '; std::cout << '\n';
return 0;
}
By the way. The generateArray fucntionality will of course also work with other generator functions.
If you need for example triangle numbers, then you could use:
constexpr size_t getTriangleNumber(size_t row) noexcept {
size_t sum{};
for (size_t i{ 1u }; i <= row; i++) sum += i;
return sum;
}
and
constexpr auto TriangleNumber = generateArray<100>(getTriangleNumber);
would give you a compile time calculated constexpr std::array<size_t, 100>
So, a rather flexible helper.
Developed and tested with Microsoft Visual Studio Community 2019, Version 16.8.2.
Additionally compiled and tested with clang11.0 and gcc10.2
Language: C++17

best way to set a bitset with boolean values

Suppose I have 3 bool type values
bool canwalk=true;
bool cantalk=false;
bool caneat=false;
I would like to set a bitset denoting the three
std::bitset<3> foo;
How can I construct a bitset using the boolean values?
I want to do something like this
std::bitset<3> foo(canWalk,cantalk,caneat); //giving me 100
Following the example of Shivendra Agarwal, but using the constructor that receive an unsigned long long, I propose the following variadic template function (to be more generic)
template <typename ... Args>
unsigned long long getULL (Args ... as)
{
using unused = int[];
unsigned long long ret { 0ULL };
(void) unused { 0, (ret <<= 1, ret |= (as ? 1ULL : 0ULL), 0)... };
return ret;
}
that permit the initialization of foo as follows
std::bitset<3> foo{ getULL(canwalk, cantalk, caneat) };
This works only if the dimension of the std::bitset isn't grater of the number of bits in an unsigned long long (with 3 whe are surely safe).
The following is a full working example
#include <bitset>
#include <iostream>
template <typename ... Args>
unsigned long long getULL (Args ... as)
{
using unused = int[];
unsigned long long ret { 0ULL };
(void) unused { 0, (ret <<= 1, ret |= (as ? 1ULL : 0ULL), 0)... };
return ret;
}
int main()
{
bool canwalk=true;
bool cantalk=false;
bool caneat=false;
std::bitset<3> foo{ getULL(canwalk, cantalk, caneat) };
std::cout << foo << std::endl;
}
IMHO, an initialization of type
std::bitset<3> foo(canWalk, cantalk, caneat);
is dangerous (error prone) because require that the template argument of std::bitset (3, in the example) correspond to the number of argument of the initialization.
I propose the creation of a "make" function (following the consolidated example of std::pair(), std::tuple(), std::make_unique(), std::make_shared) where the type and the number of arguments fix the returned type.
So I propose the following makeBitSet() function that return a std::bitset<N> where N is the number of the arguments
template <typename ... Args>
std::bitset<sizeof...(Args)> makeBitSet (Args ... as)
{
using unused = bool[];
std::bitset<sizeof...(Args)> ret;
std::size_t ui { ret.size() };
(void) unused { true, (ret.set(--ui, as), true)... };
return ret;
}
The function can be used as follows
std::bitset<3> foo{ makeBitSet(canwalk, cantalk, caneat) };
but also (better, IMHO), using the C++11 auto,
auto foo = makeBitSet(canwalk, cantalk, caneat);
Observe that, starting from C++14, makeBitSet() can use the returning auto type
template <typename ... Args>
auto makeBitSet (Args ... as)
{
// ...
avoiding the annoying std::bitset<sizeof...(Args)> redundancy.
Moreover, starting from C++17, you can use template folding and, throwing away the unused array (and the corresponding using declaration), the makeBitSet() can be simplified as [EDIT: modified, to improve performances, following a suggestion from Mooing Duck (thanks!)]
template <typename ... Args>
auto makeBitSet (Args ... as)
{
std::bitset<sizeof...(Args)> ret;
std::size_t ui { ret.size() };
( ret.set(--ui, as), ... );
return ret;
}
The following is a full working C++11 example
#include <bitset>
#include <iostream>
template <typename ... Args>
std::bitset<sizeof...(Args)> makeBitSet (Args ... as)
{
using unused = bool[];
std::bitset<sizeof...(Args)> ret;
std::size_t ui { ret.size() };
(void) unused { true, (ret.set(--ui, as), true)... };
return ret;
}
int main()
{
bool canwalk { true };
bool cantalk { false };
bool caneat { false };
auto foo = makeBitSet(canwalk, cantalk, caneat);
std::cout << foo << std::endl;
}
Introduce a new api that can give you string input that bitset accepts in parameter.
to be more generic, recommendation will be to use bool array or [std::vector<bool>][1] to get rid of these variable parameters in getString()
inline std::string getString(bool canwalk, bool canTalk, bool canEat)
{
std::stringstream input;
str << canwalk?1:0 << cantalk?1:0 << caneat?1:0;
return input.str();
}
now can define bitset as:
std::bitset<3> foo (getString(canwalk, canTalk, canEat));
You basically need a builder that will build an initial value from your boolean set to pass to the constructor of std::bitset. You can do this at compile time (as opposed to runtime) via variadic templates, like so:
template <unsigned long long initialValue>
constexpr unsigned long long bitset_value_builder_impl() { return initialValue; }
template <unsigned long long initialValue, typename First, typename ... Args>
constexpr unsigned long long bitset_value_builder_impl(First &&first, Args &&...args) {
return first ?
bitset_value_builder_impl< (initialValue | (1UL<<sizeof...(args)) ), Args...>(std::forward<Args>(args)...) :
bitset_value_builder_impl< (initialValue & ~(1UL<<sizeof...(args)) ), Args...>(std::forward<Args>(args)...);
}
template <typename First, typename ... Args>
constexpr unsigned long long bitset_value_builder(First &&first, Args &&...args) {
return bitset_value_builder_impl<0, First, Args...>(std::forward<First>(first), std::forward<Args>(args)...);
}
int main()
{
bool canwalk=true;
bool cantalk=false;
bool caneat=false;
std::bitset<3> bits{bitset_value_builder(canwalk, cantalk, caneat)};
std::cout << bits << std::endl; //100
}

Boost Hana Compile-Time List Transformation

I'm trying to figure out how to transform a list of integer constants at compile time using boost:hana.
I have my list as:
constexpr auto vals = hana::to<hana::tuple_tag>(hana::range_c<int, 0, 3>);
I want to apply the function:
constexpr auto Pow2(int i) { return 1 << i; }
However
constexpr auto res = hana::transform(list, Pow2);
produces a type for res of hana::tuple<int, int, int>. I'm not seeing how to move the argument to the lambda into a template argument for hana::int_c
// Compiler error: Non-type template argument is not a constant expression
constexpr auto Pow2(int i)
{
return hana::int_c<1 << i>{};
}
In ...
constexpr auto Pow2(int i) { return 1 << i; }
...i is a runtime integer. It is not a "compile-time-friendly" parameter, as its value is not stored as part of its type. You should pass in a int_ instead:
template <int X>
constexpr auto Pow2(hana::int_<X>) { return hana::int_c<1 << X>; }
Usage:
constexpr auto vals = hana::to<hana::tuple_tag>(hana::range_c<int, 0, 3>);
constexpr auto res = hana::transform(vals, [](auto x){ return Pow2(x); });
static_assert(std::is_same_v<
std::decay_t<decltype(res)>,
hana::tuple<hana::int_<1>, hana::int_<2>, hana::int_<4>>
>);
wandbox example
Obviously, you can also do this with a lambda. Additionally, boost::hana::int_ has an operator<< overload that returns an int_:
hana::transform(vals, [](auto x){ return hana::int_c<1> << x; });
wandbox example

Automatically identify a suitable type, large enough and precise enough, to hold the sum of all elements in a container

(This question has been dramatically edited from the original, without changing the real intent of the original question)
If we add up all the elements in a vector<int>, then the answer could overflow, requiring something like intmax_t to store the answer accurately and without overflow. But intmax_t isn't suitable for vector<double>.
I could manually specify the types:
template<typename>
struct sum_traits;
template<>
struct sum_traits<int> {
typedef long accumulate_safely_t;
};
and then use them as follows:
template<typename C>
auto sum(const C& c) {
sum_traits<decltype(c.begin())> :: accumulate_safely_t> r = 0;
for(auto &el: c)
r += el;
return r;
}
My questions: Is it possible to automatically identify a suitable type, a large and accurate type, so I don't have to manually specify each one via the type trait?
The main problem with your code is that auto r = 0 is equivalent to int r = 0. That's not how your C++98 code worked. In general, you can't find a perfect target type. Your code is just a variant of std::accumulate, so we can look at how the Standard solved this problem: it allows you to pass in the initial value for the accumulator, but also its type: long sum = std::accumulate(begin, end, long{0});
Given:
If we add up all the elements in a vector, then the answer could overflow, requiring something like intmax_t to store the answer accurately and without overflow.
Question:
My questions: Is it possible to automatically identify a suitable type, a large and accurate type, so I don't have to manually specify each one via the type trait?
The problem here is that you want to take runtime data (a vector) and from it deduce a type (a compile-time thing).
Since type deduction is a compile-time operation, we must use only the information available to us at compile time to make this decision.
The only information we have at compile-time (unless you supply more) is std::numeric_limits<int>::max() and std::numeric_limits<std::vector<int>::size_type>::max().
You don't even have std::vector<int>::max_size() at this stage, as it's not mandated to be constexpr. Neither can you rely on std::vector<int>::allocator_type::max_size() because it's:
a member function
optional
deprecated in c++17
So what we're left with is a maximum possible sum of:
std::numeric_limits<int>::max() * std::numeric_limits<std::vector<int>::size_type>::max()
we could now use a compile-time disjunction to find an appropriate integer (if such an integer exists) (something involving std::conditional)
This doesn't make the type adapt to runtime conditions, but it will at least adapt to the architecture for which you're compiling.
Something like this:
template <bool Signed, unsigned long long NofBits>
struct smallest_integer
{
template<std::size_t Bits, class...Candidates>
struct select_candidate;
template<std::size_t Bits, class...Candidates>
using select_candidate_t = typename select_candidate<Bits, Candidates...>::type;
template<std::size_t Bits, class Candidate, class...Rest>
struct select_candidate<Bits, Candidate, Rest...>
{
using type = std::conditional_t<std::numeric_limits<Candidate>::digits >= Bits, Candidate, select_candidate_t<Bits, Rest...>>;
};
template<std::size_t Bits, class Candidate>
struct select_candidate<Bits, Candidate>
{
using type = std::conditional_t<std::numeric_limits<Candidate>::digits >= Bits, Candidate, void>;
};
using type =
std::conditional_t<Signed,
select_candidate_t<NofBits, std::int8_t, std::int16_t, std::int32_t, std::int64_t, __int128_t>,
select_candidate_t<NofBits, std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t, __uint128_t>>;
};
template<bool Signed, unsigned long long NofBits> using smallest_integer_t = typename smallest_integer<Signed, NofBits>::type;
template<class L, class R>
struct result_of_multiply
{
static constexpr auto lbits = std::numeric_limits<L>::digits;
static constexpr auto rbits = std::numeric_limits<R>::digits;
static constexpr auto is_signed = std::numeric_limits<L>::is_signed or std::numeric_limits<R>::is_signed;
static constexpr auto result_bits = lbits + rbits;
using type = smallest_integer_t<is_signed, result_bits>;
};
template<class L, class R> using result_of_multiply_t = typename result_of_multiply<L, R>::type;
struct safe_multiply
{
template<class L, class R>
auto operator()(L const& l, R const& r) const -> result_of_multiply_t<L, R>
{
return result_of_multiply_t<L, R>(l) * result_of_multiply_t<L, R>(r);
}
};
template<class T>
auto accumulate_values(const std::vector<T>& v)
{
using result_type = result_of_multiply_t<T, decltype(std::declval<std::vector<T>>().max_size())>;
return std::accumulate(v.begin(), v.end(), result_type(0), std::plus<>());
}
struct uint128_t_printer
{
std::ostream& operator()(std::ostream& os) const
{
auto n = n_;
if (n == 0) return os << '0';
char str[40] = {0}; // log10(1 << 128) + '\0'
char *s = str + sizeof(str) - 1; // start at the end
while (n != 0) {
*--s = "0123456789"[n % 10]; // save last digit
n /= 10; // drop it
}
return os << s;
}
__uint128_t n_;
};
std::ostream& operator<<(std::ostream& os, const uint128_t_printer& p)
{
return p(os);
}
auto output(__uint128_t n)
{
return uint128_t_printer{n};
}
int main()
{
using rtype = result_of_multiply<std::size_t, unsigned>;
std::cout << rtype::is_signed << std::endl;
std::cout << rtype::lbits << std::endl;
std::cout << rtype::rbits << std::endl;
std::cout << rtype::result_bits << std::endl;
std::cout << std::numeric_limits<rtype::type>::digits << std::endl;
std::vector<int> v { 1, 2, 3, 4, 5, 6 };
auto z = accumulate_values(v);
std::cout << output(z) << std::endl;
auto i = safe_multiply()(std::numeric_limits<unsigned>::max(), std::numeric_limits<unsigned>::max());
std::cout << i << std::endl;
}
You can use return type deduction in C++14 just like this:
template<typename C>
auto sum(const C& c) {
auto r = 0;
for(auto &el: c)
r += el;
return r;
}
In C++11, considering your C++98 code, you may use the following:
template<typename C>
auto sum(const C& c) -> typename C::value_type {
auto r = 0;
for(auto &el: c)
r += el;
return r;
}
But, as pointed in the comments, auto r = 0; will still resolve to int at compile time. As proposed in an other answer, you may want to make the initial value type (and so the return value type) a template parameter as well:
template<typename C, typename T>
T sum(const C& c, T init) {
for(auto &el: c)
init += el;
return init;
}
// usage
std::vector<std::string> v({"Hello ", "World ", "!!!"});
std::cout << sum(v, std::string{});

How to detect if a func. is a constexpr? and mark other func. constexpr depending on it?

Assuming i have some function template f1:
template<typename f2>
int f1(int i, int j) noexcept {
return i + j + f2(i, j);
}
is there way to determine if f2(i, j) can be a constexpr. (no matter it is a func. or a functor) and so mark f1<f2> as a constexpr too?
I am thinking of using SFINAE here some how, but didn't find how to detect constexpr using type traits
You can mark f1 as constexpr.
template<typename f2>
constexpr int f1(int i, int j) noexcept {
return i + j + f2(i, j);
}
The template function f1 would be constexpr iif f2 is.
if f2 is not, you will get error only when you use f1 in a constant compile time expression.
Demo
The easiest way to check whether a function (e.g., foo) is constexpr is to assign its return value to a constexpr as below:
constexpr auto i = foo();
if the returned value is not constexpr compilation will fail.
If you want a SFINAE test to check whether a function (e.g., foo) is constexpr you could use the std::integral_constant type trait:
std::integral_constant<int, foo()>::value
Live Demo
So, finally, using Jarod42 hint, i'l write and tested this example:
#include <string>
std::string S = "123567876";
constexpr size_t p() noexcept {
return 10U;
}
template<const size_t = size_t()>
constexpr size_t f(size_t i, size_t j) noexcept {
return std::move(i + j + S.size() + p());
}
#include <iostream>
int main() {
// static constexpr const auto v = f<>(1U, 2U); // error!
std::cout << f(1U, 2U) << "\n";
return 0;
}
now it's work correctly, i tested it using GCC online complier with both C++11 and C+14.
You can proof it will be really constexpr if possible by removing '+ S.size()':
...
return std::move(i + j + p());
...
and uncommenting constexpr value:
...
static constexpr const auto v = f(1U, 2U);
std::cout << v << "\n";
...
see here.
P. S. Thank you guys!