0

The whole bunch of "stateful" metaprogramming can be achieved by forcing template reevaluation. Here I don't want to discuss whether one should or should not use or depend on it. But since C++20 has allowed usage of lambdas in unevaluated contexts, I guess the Pandora's Box is now widely open.

The following code now provides expected results, because _is_complete is now "reevaluated" on each new invocation (technically speaking, old specializations never change, only new ones are introduced).
https://godbolt.org/z/5zv884xvE

#include <cstddef>
#include <type_traits>

template <typename T, auto, typename = void>
struct _is_complete : std::false_type {};

template <typename T, auto A>
struct _is_complete<T, A, std::void_t<decltype(sizeof(T))>> : std::true_type {};

template <typename T, auto A = []{}>
constexpr auto is_complete() noexcept {
    return _is_complete<T, A>{};
}

#include <iostream>

int main() {
    std::cout << std::boolalpha;
    
    struct incomplete;
    std::cout << is_complete<incomplete>() << '\n'; // false

    struct incomplete{};
    std::cout << is_complete<incomplete>() << '\n'; // true

    return 0;
}

This particular code is closelly related to How to write is_complete template?, but I am more interested in possibility of forcing template reevaluation in general prior to C++20's lambdas.
Please, do not suggest using any kind of macros like __COUNTER__ since it would be useless within a template's declaration.

Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • 1
    You are trying to break One Definition Rule. This means Undefined Behavior. Note in linked answer anonymous namespace is used so effectively each translation unit has own namespace with separate definition of `is_complete` so ODR is not broken. – Marek R Feb 08 '23 at 15:00
  • You can use `__COUNTER__` or `__LINE__` if you change the template a little to take a `size_t` parameter and wrap it in a macro: https://stackoverflow.com/questions/55341859/meta-programming-declare-a-new-struct-on-the-fly/55342117#55342117 – NathanOliver Feb 08 '23 at 15:02
  • @NathanOliver I am not sure how it would work if I have some nested templates that rely on reevaluation of `is_complete()`. – Sergey Kolesnik Feb 08 '23 at 15:07
  • @MarekR AFAIK making a function `inline` legally allows ODR violation. That is a usefull detail, but the question's topic is somewhat different. – Sergey Kolesnik Feb 08 '23 at 15:11
  • 2
    @SergeyKolesnik not true - _"...There can be more than one definition in a program of each of the following: class type, enumeration type, __inline function__...as long as all of the following is true:..."_ _"...each definition consists of __the same sequence of tokens__ (typically, appears in the same header file)..."_ [One Definition Rule](https://en.cppreference.com/w/cpp/language/definition#One_Definition_Rule) This is because the compiler is allowed to not inline the function and instead - at link time - pick one of the function definitions at random. – Richard Critten Feb 08 '23 at 15:40
  • @RichardCritten I am not following, what conclusion do you infer? With C++20 there is no sense in pointing to **the same sequence of tokens** because each time it will be a **completely different symbol**. Different symbols with different definitions **can not** violate ODR, because every time there will be **only one** definition for each of them. – Sergey Kolesnik Feb 08 '23 at 16:08
  • @SergeyKolesnik: "*it will be a completely different symbol*" Um... why? The term "different symbol" is based on the sequence of tokens. Just because you use a lambda as a default parameter does not change this. – Nicol Bolas Feb 08 '23 at 16:32
  • @NicolBolas will I have an ODR violation if I have `template constexpr auto foo();` (which has different implementations based on `T`) and instantiate it multiple times with multiple unique user-defined types? Please, correct me if I am wrong, but as I know, each new lambda has a unique type. – Sergey Kolesnik Feb 08 '23 at 17:41
  • @SergeyKolesnik: "*as I know, each new lambda has a unique type.*" What do you mean by "new lambda" in this context? If you have a template function that returns a lambda, each instantiation will result in a different return type. But that's what the function resulting from instantiation *returns*, not what its template parameters are. – Nicol Bolas Feb 08 '23 at 17:53
  • @NicolBolas `auto A = []{}` defaulted parameter. If this expression is not required by the standard to be evaluated each time with the effects of creating "new lambda", I will agree with your point. But until now I have seen several answers like https://stackoverflow.com/a/74453799/9363996 and behavior that suggests observable effects of creating new lambda object for defaulted parameter on each invocation. – Sergey Kolesnik Feb 08 '23 at 18:10
  • @NicolBolas *"if you have a template function that returns lambda"* https://godbolt.org/z/xeo5Yndeh - I don't see how each instantiation has the same name. They are different on each instantiation, because each time a new lambda is created as a template paramater. I **would** understand that different units would have the same mangled names but with different bodies, but I **would not** understand why would lambdas be ever allowed in unevaluated context. It makes no sence to introduce a feature that can so easily blow up in your face. Smth similar: https://stackoverflow.com/q/34717823/9363996 – Sergey Kolesnik Feb 08 '23 at 19:09

0 Answers0