Showing posts with label cpp20_tmp. Show all posts
Showing posts with label cpp20_tmp. Show all posts

Nov 13, 2025

[C++][concepts] compound requirements trick

won't work after clang 16.0.0 which its more stricter, which using greedy deduction for `Any<Idxs> auto... _`
However, due to this kind of usage, C++ 26 has pack_indexing

#include <utility> // index sequence
#include <iostream> // for demo only

template<typename, size_t> concept Any = true;

auto invoke_with_last_arg(auto f, auto... args) {

    return [&]<size_t... Idxs>(std::index_sequence<Idxs...>) {
        // compound requirements, Any<Idxs> is assigned as: 
        // Any<decltype(_), size_t>
        return [&](Any<Idxs> auto... _, auto last) {
            return f(last);
        }(args...);
    }(std::make_index_sequence<sizeof...(args)-1>{});

}

void demo(auto... args) {
    invoke_with_last_arg(
        [](auto last_arg) { std::cout << last_arg << std::endl; },
        args...
    );
}

int main() { demo(3.14, 42, "hello world"); }

Dec 19, 2022

[C++] non-deduced context usage in template function parameter deduction.

Interesting question raised in stackoverflow about non-deduced context usage for deduced type for function.

Explained inline:

#include <iostream>
#include <functional>
#include <type_traits>
using namespace std;

/**
 * foo shall fail due to TArgs's first 2 specialized argument are;
 * in our example below in main, (int, int);
 * however, the deduce of packing continues from the argument in case needs to be extended;
 * thus TArgs is deduced to empty pack(because [](int,int){} has both int args matched),
 * and std::function<void(empty)> can not take [](int,int){};
 */
template <typename... TArgs>
void foo(std::function<void(TArgs...)> f) {
}

/**
 * Non-deduced contexts compes into the play.
 * https://vsdmars.blogspot.com/2022/07/c20-stdtypeidentity-for-non-deduced.html
 * Type inside std::type_identity_t<...> is replaced not deduced.
 */ 
template <typename... TArgs>
void foo_cure(std::type_identity_t<std::function<void(TArgs...)>> f) {
}

int main() { 
    // foo<int,int>([](int, int){}); // no can do.
    foo_cure<int,int>([](int, int){});
}

code in action:
https://godbolt.org/z/qrG5G53ar

There are other ways around for OP's question, e.g. using class template with static member function
taking the specialized class template arguments for the static member function; or, simply,
use universal reference template for the standalone template function taking the lambda expression object with variable argument-types.

Alas, while start coding in Rust, C++ makes me feel taking more time ruminating for the better solution than solving the problem, kinda like Perl?! In the long run I bet Rust shall take over; however, template programming is fun. 😄

Further read:
[C++20] std::type_identity for Non-deduced contexts

Jul 6, 2022

[C++20] std::type_identity for Non-deduced contexts

Reference:
https://vsdmars.blogspot.com/2018/05/caccu2018-tricks-library-implementation.html


While reading {fmt} code:
https://github.com/fmtlib/fmt/blob/e6d478f8e88a357671c39bd39922ecac32aa2f58/include/fmt/core.h#L3107

template <typename... Args>
using format_string = basic_format_string<char, type_identity_t<Args>...>;
wondering why is type_identity_t needed?

This is due to how arguments participate function parameter deduction.

This is sort of template deduction 101, i.e. for parameters there's no implicit conversion involved.

Thus in order to avoid ambiguity from the deduction rule imposed on compiler,
introducing a Non-deduced contexts which uses the deduced type explicitly(which avoids implicit conversion at compile time) to remove ambiguity and allow implicit conversion at runtime.

e.g.
// the identity template, often used to exclude specific arguments from deduction
// (available as std::type_identity as of C++20)
template<typename T>
struct identity { typedef T type; };
 
template<typename T>
void bad(std::vector<T> x, T value = 1);
 
template<typename T>
void good(std::vector<T> x, typename identity<T>::type value = 1);
 
std::vector<std::complex<double>> x;
 
bad(x, 1.2);  // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
              // P1/A1: deduced T = std::complex<double>
              // P2 = T, A2 = double
              // P2/A2: deduced T = double
              // error: deduction fails, T is ambiguous
 
good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>>
              // P1/A1: deduced T = std::complex<double>
              // P2 = identity<T>::type, A2 = double
              // P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2
              // OK: T = std::complex<double>

Jun 24, 2022

[C++] Stateful Metaprogramming in C++20

Reference:
https://mc-deltat.github.io/articles/stateful-metaprogramming-cpp20

constexpr template function instantiation could be elided by compiler thus in order to assure template function being instantiated, use constexpr variadic variable instead; which is also a template but assure being instantiated.

Below code demonstrates even though the being generated at compile time the result can be different due to during the compile time state changes.


Example 1:
// declare of flag function, the function body doesn't exist
// unless template type setter being instantiated.
auto flag(int);

template<bool B> requires (!B)
struct setter {
	// 'flag' definition
    friend auto flag(int) {}

    static constexpr bool b = B;
};


// declare [[nodiscard]] and consteval make sure the
// template function won't be elided by compiler.
template<bool FlagVal>
[[nodiscard]]
consteval auto nonconstant_constant_impl() {
    if constexpr (FlagVal) {
        return true;
    }
    else {
    // 'setter' being instantiated.
        setter<FlagVal> s;
        return s.b;
    }
}


template<
    auto Arg = 0,
    bool FlagVal = requires { flag(Arg); },
    auto Val = nonconstant_constant_impl<FlagVal>()
>
constexpr auto nonconstant_constant = Val;

auto main() ->int{
    // a = 0
    // First evaluation in this TU; triggers 'setter' being
    // instantiated.
    // a = 0 (False)
    constexpr bool a = nonconstant_constant<>;      

    // b = 1 (True)
    constexpr bool b = nonconstant_constant<>;

    // assertion passes.
    static_assert(a != b);
}

Example 2:
template<unsigned N>
struct reader {
    friend auto counted_flag(reader<N>);
};


template<unsigned N>
struct setter {
    friend auto counted_flag(reader<N>) {}

    static constexpr unsigned n = N;
};



template<
    auto Tag,
    unsigned NextVal = 0
>
[[nodiscard]]
consteval auto counter_impl() {
    constexpr bool counted_past_value = requires(reader<NextVal> r) {
        counted_flag(r);
    };

    if constexpr (counted_past_value) {
        return counter_impl<Tag, NextVal + 1>();
    }
    else {

        setter<NextVal> s;
        return s.n;
    }
}


template<
    auto Tag = []{}, // Each call generates different type.
    auto Val = counter_impl<Tag>()
>
constexpr auto counter = Val;



int main() {
    static_assert(counter<> == 0);
    static_assert(counter<> == 1);
    static_assert(counter<> == 2);
    static_assert(counter<> == 3);
    static_assert(counter<> == 4);
    static_assert(counter<> == 5);
    static_assert(counter<> == 6);
    static_assert(counter<> == 7);
    static_assert(counter<> == 8);
    static_assert(counter<> == 9);
    static_assert(counter<> == 10);
}

Example 3:
s.t I've played around back in 2013
#include <concepts>
#include <type_traits>


template<typename...>
struct type_list {};


template<class TypeList, typename T>
struct type_list_append;

template<typename... Ts, typename T>
struct type_list_append<type_list<Ts...>, T> {
    using type = type_list<Ts..., T>;
};


template<unsigned N, typename List>
struct state_t {
    static constexpr unsigned n = N;
    using list = List;
};


namespace {
    // used in reader; thus the 'state_func' shall be unique in each TU
    // thus not violating the ODR
    struct tu_tag {};
}


template<
    unsigned N,
    std::same_as<tu_tag> TUTag
>
struct reader {
    friend auto state_func(reader<N, TUTag>);
};


template<
    unsigned N,
    typename List,
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag
>
struct setter {
    // generated 'state_func' will be unique in each TU thanks to 'TUTag'
    friend auto state_func(reader<N, TUTag>) {
        return List{};
    }

    static constexpr state_t<N, List> state{};
};


template struct setter<0, type_list<>, tu_tag>;


template<
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag,
    auto EvalTag,
    unsigned N = 0
>
[[nodiscard]]
consteval auto get_state() {
    constexpr bool counted_past_n = requires(reader<N, TUTag> r) {
        state_func(r);
    };

    if constexpr (counted_past_n) {
        return get_state<TUTag, EvalTag, N + 1>();
    } else {
        constexpr reader<N - 1, TUTag> r;
        return state_t<N - 1, decltype(state_func(r))>{};
    }
}


template<
	// std::same_as is a concept; taking 2 arguments. <...> binds to back paramters while
	// assigned type is bound to forefront paramter.
    // Use to prevent accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag = tu_tag,
    auto EvalTag = []{},
    auto State = get_state<TUTag, EvalTag>()
>
using get_list = typename std::remove_cvref_t<decltype(State)>::list;


template<
    typename T,
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonymous type 'tu_tag'
    std::same_as<tu_tag> TUTag,
    auto EvalTag
>
[[nodiscard]]
consteval auto append_impl() {
    using cur_state = decltype(get_state<TUTag, EvalTag>());
    using cur_list = typename cur_state::list;
    using new_list = typename type_list_append<cur_list, T>::type;
    setter<cur_state::n + 1, new_list, TUTag> s;
    return s.state;
}


template<
    typename T,
    // Preventing accidentally passing random type thus violating ODR
    // It must be anonmous type 'tu_tag'
    std::same_as<tu_tag> TUTag = tu_tag,
    auto EvalTag = []{},
    auto State = append_impl<T, TUTag, EvalTag>()
>
constexpr auto append = [] { return State; };


int main() {
    static_assert(std::same_as<get_list<>, type_list<>>);

    append<int>();
    static_assert(std::same_as<get_list<>, type_list<int>>);

    append<float>();
    static_assert(std::same_as<get_list<>, type_list<int, float>>);

    append<char>();
    static_assert(std::same_as<get_list<>, type_list<int, float, char>>);
}

Jun 13, 2022

[C++][C++20] compile time heap allocate

Reference:

Compile-time functions can allocate memory provided the memory is also released at compile time.

For this reason, you can now use strings or vectors at compile time. 
However, you cannot use the compile-time created strings or vectors at runtime because memory allocated at compile time has to be released at compile time.
#include <vector>
#include <ranges>
#include <algorithm>
#include <numeric>

template<std::ranges::input_range T>
constexpr auto modifiedAvg(const T& rg) {
    using elemType = std::ranges::range_value_t<T>;
    // initialize compile-time vector with passed elements:
    std::vector<elemType> v{std::ranges::begin(rg),
    std::ranges::end(rg)};
    // perform several modifications:
    v.push_back(elemType{});
    std::ranges::sort(v);
    auto newEnd = std::unique(v.begin(), v.end());

    // return average of modified vector:
    auto sum = std::accumulate(v.begin(), newEnd, elemType{});
    return sum / static_cast<double>(v.size());
}

// 注意,要用constexpr不然modifiedAvg為runtime.
constexpr auto avg = modifiedAvg(orig);


// use concept
// initialize compile-time vector with passed elements
template<std::ranges::input_range T>
consteval auto modifiedAvg(T rg) {
    using elemType = std::ranges::range_value_t<T>;
    std::vector<elemType> v{std::ranges::begin(rg), std::ranges::end(rg)};
}

However, note that we still cannot declare and initialize a vector at compile time that is usable at runtime:
int main() {
    constexpr std::vector orig{0, 8, 15, 132, 4, 77}; // ERROR
}

For the same reason, a compile-time function can only return a vector to the caller when the return value is used at compile time:
#include <vector>

constexpr auto returnVector() {
    std::vector<int> v{0, 8, 15};
    v.push_back(42);
    return v;
}

constexpr auto returnVectorSize() {
    constexpr auto coll = returnVector();
    return coll.size();
}

int main() {
    // constexpr auto coll = returnVector(); // ERROR
    constexpr auto tmp = returnVectorSize();
}
#include <vector>
#include <ranges>
#include <algorithm>
#include <array>

template<std::ranges::input_range T>
consteval auto mergeValuesSz(T rg, auto... vals) {
// create compile-time vector:
std::vector<std::ranges::range_value_t<T>> v{
    std::ranges::begin(rg), std::ranges::end(rg)};

    (... , v.push_back(vals)); // and merge passed values
    std::ranges::sort(v);

    constexpr auto maxSz = rg.size() + sizeof...(vals);
    std::array<std::ranges::range_value_t<T>, maxSz> arr{};
    auto res = std::ranges::unique_copy(v, arr.begin());

    return std::pair{arr, res.out - arr.begin()};
}

Using Strings at Compile Time:
Rule of thumb: cannot use a compile-time string at runtime.
String SSO implement also take into account. (https://godbolt.org/z/eTYfcfMc5)



constexpr Language Extensions

Since C++20, the following language features are possible to be used in compile time functions (whether declared with constexpr or consteval):
  • You can now use heap memory at compile time.
  • Runtime polymorphism is supported:
    • You can now use virtual functions.
    • You can now use dynamic_cast.
    • You can now use typeid.
  • You can have try-catch blocks now (but you are still not allowed to throw).
  • You can now change the active member of a union.
  • Note that you are still not allowed to use static in constexpr or consteval functions.


lamdba

template<typename... Args>
void foo(Args... args) {
    // OK since C++20
    auto l4 = [...args = std::move(args)] {
        bar(args...); // OK
    };
}

template<typename... Args>
void foo(Args... args) {
    auto l4 = [&...fooArgs = args] {
        bar(fooArgs...); // OK
    };
}



new type:
char8_t
std::u8string
std::u8string_view

char8_t c = u8'@';      // character with UTF-8 encoding for character @
const char8_t* s = u8"K\u00F6ln";       // character sequence with UTF-8 encoding for Köln



#include <iostream>
#include <cmath>
#include <thread>
#include <syncstream>

void squareRoots(int num) {
    for (int i = 0; i < num ; ++i) {
        std::osyncstream coutSync{std::cout};
        coutSync << "squareroot of " << i << " is "
            << std::sqrt(i) << '\n';
    }
}

int main() {
    std::jthread t1(squareRoots, 5);
    std::jthread t2(squareRoots, 5);
    std::jthread t3(squareRoots, 5);
}


For writing to file:
#include <fstream>
#include <cmath>
#include <thread>
#include <syncstream>

void squareRoots(std::ostream& strm, int num) {

    std::osyncstream syncStrm{strm};

    for (int i = 0; i < num ; ++i) {
        syncStrm << "squareroot of " << i << " is "
            << std::sqrt(i) << '\n' << std::flush_emit;
    }
}


int main() {
    std::ofstream fs{"tmp.out"};
    std::jthread t1(squareRoots, std::ref(fs), 5);
    std::jthread t2(squareRoots, std::ref(fs), 5);
    std::jthread t3(squareRoots, std::ref(fs), 5);
}
or:
#include <fstream>
#include <cmath>
#include <thread>
#include <syncstream>

void squareRoots(std::ostream& strm, int num) {
    for (int i = 0; i < num ; ++i) {
        strm << "squareroot of " << i << " is "
        << std::sqrt(i) << '\n' << std::flush_emit;
    }
}

int main() {
    std::ofstream fs{"tmp.out"};
    std::osyncstream syncStrm1{fs};
    std::jthread t1(squareRoots, std::ref(syncStrm1), 5);

    std::osyncstream syncStrm2{fs};
    std::jthread t2(squareRoots, std::ref(syncStrm2), 5);

    std::osyncstream syncStrm3{fs};
    std::jthread t3(squareRoots, std::ref(syncStrm3), 5);
}

Nov 27, 2018

[C++][cppcon 2018] How to Write Well-Behaved Value Wrappers - Simon Brand


Value Wrappers

Types with value-semantics which can store objects of any type.
Do what 'int' does.
i.e
std::pair
std::optional
std::variant


Traits that value wrappers should have

  • Performant
  • Unsurprising


Hey, beware of code's 'hot paths'!


Let's start:

Comparison Operators

Weak ordering, i.e equivalence rather than equality.
Strong ordering, i.e equality rather than equivalence.

Relation strength
(a <=> b) < 0  //true if a < b
(a <=> b) > 0  //true if a > b
(a <=> b) == 0 //true if a is equal/equivalent to b


spaceship operator returns types as follows:
Five types are provided, and stronger relations can implicitly convert to weaker ones:

(figure credits: https://blog.tartanllama.xyz/spaceship-operator/ )


Why those types?
1. Indicates to the user what kind of relation is modeled by the comparisons.
2. Algorithms takes advantage of being optimized.
i.e If the operands are compared equal with strongly-ordered type, it indicates
that any function taking either operand should give out the same result, thus
call the function once with a operand would be enough.
3. These types can be used to define language features, i.e
Class Types in Non-Type Template Parameters
( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0732r0.pdf ),
i.e reduce template instantiation if the non-value type argument is strongly-ordered equal.

spaceship operator doesn't need to be free function like binary operators:
struct foo {
  int i;

  std::strong_ordering operator<=> (foo const& rhs) {
    return i <=> rhs.i;
  }
};

Use std::compare_three_way to fall back to two-way comparisons if there's
no three-way comparisons available.

Write a three-way comparisons operator for std::pair:
template<class T, class U>
struct pair {
  T t;
  U u;

  auto operator<=> (pair const& rhs) const
    -> std::common_comparison_category_t<
         decltype(std::compare_three_way{}(t, rhs.t)),
         decltype(std::compare_three_way{}(u, rhs.u)> {
    if (auto cmp = std::compare_three_way{}(t, rhs.t); cmp != 0) return cmp;
    return std::compare_three_way{}(u, rhs.u);
  }

std::common_comparison_category_t determines the weakest relation
in it's arguments.
i.e
std::common_comparison_category_t<std::strong_ordering, std::partial_ordering>
is std::partial_ordering

C++20 supports automatic generation of comparison operators.
auto operator<=>(x const&) = default;

Reference:
Implementing the spaceship operator for optional
Spaceship Operator
https://en.cppreference.com/w/cpp/header/compare


'noexcept' Propagation

Propagate the noexcept-ness of 'move' and 'swap' operations.
Why? Cause std containers honor noexcept 'move' constructor.


'explicit' or not 'explicit'

// OK but with code duplication.
template<typename U, std::enable_if_t<
    std::is_constructible<T, U>::value &&
    std::is_convertible<U, T>::value>* = nullptr>
wrapper(U&& u) : t(std::forward<U>(u)) {}


template<typename U, std::enable_if_t<
    std::is_constructible<T, U>::value &&
    !std::is_convertible<U, T>::value>* = nullptr>
explicit wrapper(U&& u) : t(std::forward<U>(u)) {}
to:
template<typename U, std::enable_if_t<
    std::is_constructible<T, U>::value>* = nullptr>
explicit(std::is_convertible<U, T>::value>)   // https://en.cppreference.com/w/cpp/language/explicit C++20
wrapper(U&& u) : t(std::forward<U>(u)) {}


Conditionally deleting special members

once touches the Rule of 5, it's zero-sum situation.
Use base class for rule of 5's delete/default and use private inherits.
Use concept.
optional(optional const& rhs) {
    if (rhs.enaged) {
        new (std::addressof(t)) T (rhs.t);
        engaged = true;
    }
}


Triviality propagation

'An object with either a non-trivial copy constructor or a non-trivial destructor
cannot be passed by value because such objects must have well defined addresses.'
This effects RVO.
We cannot use SFINAE on destructor/copy constructor.
However, we can again use base type and with private inherits.
Use concept to simplify the code.


Ref-qualified accessor functions:
template<typename Self>
decltype(auto) operator*(this Self&& self) {
    return std::forward<Self>(self).m_value;
}


SFINAE-unfriendly callables

Expression SFINAE: https://stackoverflow.com/a/12654277
consteval in C++20
SFINAE only works at:
quoted:
--
Only the failures in the types and expressions in the immediate context of the function type or its template parameter types or its explicit specifier (since C++20) are SFINAE errors.

If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors.

A lambda expression is not considered part of the immediate context. (since C++20)
--
Be ware of SFINAE's hard errors.
#include <iostream>
#include <memory>

using namespace std;

struct foo {
    void do_thing()
    {
        cout << "do thing" << endl;
    }
};

template <typename T>
struct wrapper {
    T t;
    template <typename F>
    auto pass_to(F f) -> decltype(f(t)) // expression SFINAE check
    {
        f(t);
        cout << "no const" << endl;
    }

    template <typename F>
    auto pass_to(F f) const -> decltype(f(t)) // expression SFINAE check
    {
        f(t);
        cout << "const" << endl;
    }

    template <typename F>
    auto pass_to_no_decltype(F f)
    {
        f(t);
        cout << "no const" << endl;
    }

    template <typename F>
    auto pass_to_no_decltype(F f) const
    {
        f(t);
        cout << "const" << endl;
    }
};


int main()
{
    const wrapper<foo> f{foo{}};
    // hard error; error: 'this' argument to member function 'do_thing' has type 'const foo', but function is not marked const
    // f.pass_to([](auto &&x) { x.do_thing(); }); 

	// hard error;  error: 'this' argument to member function 'do_thing' has type 'const foo', but function is not marked const
    // f.pass_to([](auto &x) { x.do_thing(); });  

    f.pass_to([](auto x) { x.do_thing(); });

    // Using const this to differentiate.
    f.pass_to_no_decltype([](auto &&x) { x.do_thing(); });
    f.pass_to_no_decltype([](auto &x) { x.do_thing(); });
    f.pass_to_no_decltype([](auto x) { x.do_thing(); });
}

Simon uses 'this Self&& self' signature,
i.e take advantage of re-appearing the
'this' pointer(r-value), like in Python, to solve the problem,
due to 'this' is c.v qualified.
template<typename T>
struct wrapper {
    T t;
    template<typename Self, typename F>
    auto pass_to (this Self&& self, F f) -> decltype(f(self.t)) {
        f(t);
    }
};

Aug 19, 2018

[C++20] String literals as non-type template parameters

Follow up with previous post and why the 'WTH?!" :-P
http://vsdmars.blogspot.com/2018/08/c20-compile-time-regex.html


Background knowledge:

7.5.1 Literals [expr.prim.literal]
1 #A literal is a primary expression. Its type depends on its form. A string literal is an lvalue; all other literals are prvalues.

i.e const char* _ = "XXX"

Template non-type arguments can't be internal linkage pointer, in this proposal, the implement becomes: (excerpt from pdf)

The idea behind how this would work is that the compiler would generate a constexpr array and pass a reference to that as a template argument:
template <auto& str>
void f()
{ 
// str is a 'char const (&)[7]'
} 

f<"foobar">(); // should be roughly equivalent to inline constexpr char

__unnamed[] = "foobar";

f<__unnamed>();

Calling a function template with such a template-parameter-list works in both Clang and GCC today.
---------
Once it becomes char [] in a TU, it's location is settled at compile time,
thus, template can be instantiated~~~
Nice~

What about ODR?

If it's instantiated in the .cpp, there's no ODR violation due to difference address of pointer generated different type of template instance(name mangled)

If it's instantiated in the .h, the proposal suggests 'should not be an ODR violation' since it's inlined thus with external linkage thus with one single instance of the template being instantiated.


Once again, compatible to C having all this interesting, auh, engineering work around. :-p

Reference:

[C++] char string literal is l-value

P0424R2 Louis Dionne & Hana Dusíková

Compiler tool chain linking notes

[C][C++] difference between char array[] and char *array, why char [] not char* could be used in non-type argument for template.