Home > Enterprise >  Instantiating templates based on a non-template argument works for GCC but not for CLANG
Instantiating templates based on a non-template argument works for GCC but not for CLANG

Time:01-12

A question for language lawyers: is my code ill-formed and happens to work on GCC, or on the contrary, is the CLANG incompatible with the standard?

When instantiating a template with a member or non-type parameter CLANG complains:

error: non-type template argument refers to subobject

I'm trying to have some memory generated by the compiler using a template structure with a static array member. The compiler should statically allocate the array memory for each instantiated structure.

I would like to "configure" those memory pools with a constexpr array. Then generate the pools during the compilation time, into another array.

The below code works for GCC (I'm using 11.3, the trunk version also works). But it doesn't work for CLANG.

If that's a CLANG issue, is there a workaround for it?

GODBOLT

struct NamedPoolConfig {
    const std::string_view name;
    const unsigned int pool_size;
};

inline constexpr std::array pool_config = std::to_array<const NamedPoolConfig>({
        {  .name = "A", .pool_size = 512,  },
        {  .name = "B", .pool_size = 1024, },
        {  .name = "C", .pool_size = 256,  },
});

template<std::string_view const& PoolName, std::size_t SIZE>
struct StaticPool {
    inline static std::array<std::uint8_t, SIZE> static_arr;
    static constexpr std::span<std::uint8_t> pool = std::span(static_arr.data(), static_arr.size());
};

struct NamedPool {
    std::string_view name;
    std::span<std::uint8_t> pool;
};

template<const decltype(pool_config)& arr>
consteval auto make_pools() {
    return []<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::array{NamedPool{
                .name = std::string_view(arr[Is].name),
                .pool = StaticPool<arr[Is].name, arr[Is].pool_size>::pool,
        }...};
    }(std::make_index_sequence<arr.size()>{});
}

inline auto pool_array = make_pools<pool_config>();

I'm slightly inclining toward the fact that my code is hacky because the compiler instantiates those static pools not based on the names I've given but based on the variable names. This would likely break any future code with multiple pool arrays, but that's a separate question.

    StaticPool<((pool_config._M_elems)[0l]).name, 512ul>::static_arr:
           .zero   512

CodePudding user response:

In C 17 it was illegal for template arguments of non-type template parameters to be any kind of subobject, as per [temp.arg.nontype]/2:

A template-argument for a non-type template-parameter shall be a converted constant expression of the type of the template-parameter. For a non-type template-parameter of reference or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):

  • (2.1) a subobject,
  • [...]

CWG 2043 pointed out that was overly restrictive, but never made it beyond drafting.

As of C 20 the limitations have been relaxed; [temp.arg.nontype]/3

For a non-type template-parameter of reference or pointer type, or for each non-static data member of reference or pointer type in a non-type template-parameter of class type or subobject thereof, the reference or pointer value shall not refer to or be the address of (respectively):

  • (3.1) a temporary object ([class.temporary]),
  • (3.2) a string literal object ([lex.string]),
  • (3.3) the result of a typeid expression ([expr.typeid]),
  • (3.4) a predefined __func__ variable ([dcl.fct.def.general]), or
  • (3.5) a subobject ([intro.object]) of one of the above.

In OP's example, arr[Is].name is a subobject of a NamedPoolConfig object and in turn a subobject of an array of such objects, but none of these fit (3.1) through (3.4) above, meaning C 20's [temp.arg.nontype]/3 does not apply, and the example is well-formed.

We may note that P0732R2 (Class Types in Non-Type Template Parameters) was not as lenient as the current wording:

In keeping with our conceptual model, the restrictions on non-type template arguments of reference and pointer type in [temp.arg.nontype]p2 should apply to all reference- and pointer-type subobjects of class objects used as non-type template arguments.

and kept the wider subobject restriction even after the introduction of class types in non-type template arguments. The subsequent paper P1907R1 (Inconsistencies with non-type template parameters), however, relaxed the wording to its current state.

Thus, this is either a clang bug, or possibly more likely a not-yet-implemented feature, given that Clang's C 20 implementation status lists implementation of P1907R1 only as Partial. We may finally note that GCC, on the other hand, lists full support for this feature as of GCC 11, which is in line with OP's experiments:

P1907R1

  • 10 (no floating point, union, or subobject template args)
  • 11 (complete)

CodePudding user response:

For anyone interested in the workaround. Just pass the array and the index to the troublesome template.

struct NamedPoolConfig {
    const std::string_view name;
    const unsigned int pool_size;
};

inline constexpr std::array pool_config = std::to_array<const NamedPoolConfig>({
        {  .name = "A", .pool_size = 512,  },
        {  .name = "B", .pool_size = 1024, },
        {  .name = "C", .pool_size = 256,  },
});

template<const decltype(pool_config)& arr, std::size_t Idx>
struct StaticPool {
    static constexpr auto& cfg = arr[Idx];
    inline static std::array<std::uint8_t, cfg.pool_size> static_arr;
    static constexpr std::span<std::uint8_t> pool = std::span(static_arr.data(), static_arr.size());
};

struct NamedPool {
    std::string_view name;
    std::span<std::uint8_t> pool;
};

template<const decltype(pool_config)& arr>
consteval auto make_pools() {
    return []<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::array{NamedPool{
                .name = std::string_view(arr[Is].name),
                .pool = StaticPool<arr, Is>::pool,
        }...};
    }(std::make_index_sequence<arr.size()>{});
}

inline auto pool_array = make_pools<pool_config>();

Highlighting the required changes:

template<const decltype(pool_config)& arr, std::size_t Idx>
struct StaticPool {
    static constexpr auto& cfg = arr[Idx];
    inline static std::array<std::uint8_t, cfg.pool_size> static_arr;
    .pool = StaticPool<arr, Is>::pool,

GODBOLT

  • Related