Is generating unique ID from template template parameters UB?
Asked Answered
F

1

8

I am trying to generate unique IDs from template template parameters. I tried this function

inline size_t g_id = 1;

template<template<typename> typename T>
inline size_t GetID()
{
    static size_t id = g_id++;
    return id;
}

it works fine until used with alias templates

template<template<typename> typename T>
inline void print()
{
    std::cout << GetID<T>() << "\n";
}

template<typename T>
struct S {};
struct W1 { template<typename A> using type = S<A>; };
struct W2 { template<typename A> using type = S<A>; };

int main()
{
    print<S>();
    print<W1::type>();
    print<W2::type>();

    std::cin.get();
}

MSVC

1
2
3

clang

1
2
3

gcc

1
1
1

Is any compiler correct here or is there UB somewhere?

Update

After reading some of the material linked from Davis Herring`s comment CG1286, an alias template does not need to have the same template name as the underlying template. To me this seems like it could go both ways so are all compilers compliant here?

With that I have come up with a different way to generate IDs from template template parameters which should avoid this problem but has some compromises. Requires that you specialize the template with a Tag type and create a static method which retrieves your ID.

Implementation

inline size_t g_id = 1;

template<typename T>
inline size_t GenerateID()
{
    static size_t id = g_id++;
    return id;
}

struct Tag {};

template<template<typename...> typename T, typename... Args, typename = decltype(sizeof(T<Args...>))>
inline size_t get_id_imp(int)
{
    return T<Args...>::GetID();
}

template<template<typename...> typename T, typename... Args, std::enable_if_t<sizeof...(Args) < 16, bool> = true>//16 = max template args
inline size_t get_id_imp(...)
{
    return get_id_imp<T, Args..., Tag>(0);
}

template<template<typename...> typename T, typename... Args, std::enable_if_t<sizeof...(Args) >= 16, bool > = true>
inline size_t get_id_imp(...)
{
    return 0;
}

template<template<typename...> typename T>
inline size_t GetID()
{
    return get_id_imp<T, Tag>(0);
}

Use

template<typename T>
struct X {};
template<> struct X<Tag> { static size_t GetID() { return GenerateID<X>(); } };

template<template<typename...> typename T>
inline void print()
{
    std::cout << GetID<T>() << "\n";
}
Fuentes answered 31/10, 2021 at 14:45 Comment(1)
I don’t think it qualifies as a duplicate, but it’s the same bug recently encountered.Sulcus
E
1

There is no UB here. The template GetID is instantiated once for each unique template argument, but GCC wrongly treats the alias templates as the template they alias itself, because they are equivalent here, as Davis Herring pointed out.

I think the simplest general solution is to pass the argument types in the alias templates through another alias template that makes them dependent names.

template<class Type> struct typeAlias { using AliasedType = Type; };
template<class Type> using AliasType = typename typeAlias<Type>::AliasedType;

template<typename T>
struct S {};
struct W1 { template<typename A> using type = S<AliasType<A>>; };
struct W2 { template<typename A> using type = S<AliasType<A>>; };
Estaestablish answered 19/11, 2021 at 14:44 Comment(2)
Thanks, now that there is a general solution to make GCC conform I wonder if there is also a general solution to get GCC`s behavior. That is alias templates being equivalent to the alias itself. As I think would be the desired behavior for a template template ID generator.Fuentes
@Fuentes I don't think you would want to reproduce this behavior with these semantics. Depending on different configurations of template argument and parameter lists, they may be treated as equivalent or not, and from the perspective of using such alias templates as template arguments, you can't easily see that, especially in more complex programs.Estaestablish

© 2022 - 2024 — McMap. All rights reserved.