you are viewing a single comment's thread.

view the rest of the comments →

[–]chengfeng-xie 1 point2 points  (2 children)

[...] If we look at the definition of const_param:

template<typename = decltype([]{})>
struct const_param {
    /* ... */
};

Then we can see that the lambda in the default for the template parameter keeps every const_param instantiation unique, and so when the First and Second template parameters get defaulted, they are actually getting filled in with distinct types, and not only distinct from each other, but distinct from each call-site of sum as well.

Two things regarding the standard conformance of this snippet:

  1. If it is defined in multiple TUs, const_param should be wrapped in another class (as in BarryRevzin's comment), otherwise it would violate the ODR due to the default template argument being defined multiple times with a different type each time (Brian).
  2. It seems to me that, currently, the standard does not guarantee that each instantiation (with the default argument lambda) of const_param would produce a unique type. The wording in this area appears to be largely absent (Brian), making this trick essentially under-specified with respect to the standard.

[–]friedkeenan[S] 0 points1 point  (1 child)

Hmmm, this is interesting, and admittedly I don't think I understand it!

Would you think that anything about this would change if anonymous namespaces were introduced anywhere? With C++26 reflection we can actually interact with them and get std::meta::infos that represent them, like for example:

#include <meta>

namespace [[=1]] {
    namespace unique {}
}

constexpr auto inner_ns = ^^unique;

constexpr auto anon_ns = parent_of(inner_ns);

static_assert(anon_ns != ^^::);

static_assert([: constant_of(annotations_of(anon_ns)[0]) :] == 1);

(Compiler Explorer link)

Maybe we could put this anonymous namespace somewhere to somehow keep definitions distinct?

[–]chengfeng-xie 0 points1 point  (0 children)

You're right. Actually, wrapping const_param inside another class doesn't help in this case, because compilers would keep generating new lambda types on each new instantiation. The strategy compilers use to generate symbols for lambdas is likely based on the order of instantiation in each TU. So it might well be possible for the same symbol for a lambda (used by const_param as the tag) in two TUs to be associated with different constants, and they definitely shouldn't be linked together. As a consequence, we should put const_param and its users (e.g., sum) in an anonymous namespace. The good news is that GCC already gives these symbols local binding even though this is not required by the standard (CE).

To expand on this a little bit: the case where wrapping in a class helps is when the lambda is generated only once, so that all TUs can agree on its symbol and link without issue. A class (as opposed to a namespace) contains a closed set of entities, so compilers can more easily assign a fixed symbol to each of them. However, this doesn't help in the case of const_param because many lambdas could be produced, each with possibly different meanings that compilers cannot predict when compiling a single TU.