Is it possible to force template reevaluation prior to C++20?
Asked Answered
R

0

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.

Rheumatism answered 8/2, 2023 at 14:53 Comment(11)
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.Stelu
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: #55342359Easterner
@Easterner I am not sure how it would work if I have some nested templates that rely on reevaluation of is_complete<T>().Rheumatism
@MarekR AFAIK making a function inline legally allows ODR violation. That is a usefull detail, but the question's topic is somewhat different.Rheumatism
@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 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.Beeswax
@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.Rheumatism
@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.Roca
@NicolBolas will I have an ODR violation if I have template <typename T> 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.Rheumatism
@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.Roca
@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://mcmap.net/q/20768/-does-c-support-compile-time-counters and behavior that suggests observable effects of creating new lambda object for defaulted parameter on each invocation.Rheumatism
@NicolBolas "if you have a template function that returns lambda" 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://mcmap.net/q/25282/-can-using-a-lambda-in-header-files-violate-the-odr/9363996Rheumatism

© 2022 - 2024 — McMap. All rights reserved.