Why are variable types that appear the same not being recognized as the same?

99 Views Asked by At

I really don't know what to do with this. It seems code is behaving differently depending on where a variable (or its type) originates. And I don't know how to work around this to get consistent behavior.

Below (and linked) is a distilled version of some code being developed for fmtster.

In this code, specialized structures are created for a specific types (float and std::string in the example). Some member functions in the templatized structures (not shown in the example) are then called by the framework ({fmt} or std::format).

is_foobar_v<> is designed to check whether, for the specified type, there is a specialization of the foo structure which has bar as its base class. When the specific type is used as the template argument, it works correctly. Also, when decltype() is used with the local variables, it also works correctly:

  • is_foobar_v<int> returns false, because foo<int> does not use bar as a base class
  • is_foobar_v<float> returns true, because foo<float> does use bar as a base class.
  • is_foobar_v<string> returns true, because foo<string> does use bar as a base class.

(Clearly use of decltype() is not an issue.)

However, next in the code is one example from two places where is_foobar_v<> fails. The code below steps through the elements of a tuple<>, checking whether a foo<> structure, using that type as the template argument, is based on bar. is_foobar_v<> always returns false.

Note that I've also added an explicit instantiation of foo<> objects, to show which constructors are called. You'll note that these logs show that the code does not instantiate the proper specialization inside the lambda. Oddly, in the actual code, the instantiation actually works correctly, even though my check fails. However, I was not able to figure out why it fails to instantiate the correct specialization structure in this distilled code. After most of a day wasted trying, I'm hoping that is not related to the check failure.

I want to reiterate that this is one of two places where I have encountered this. The other was in a simpler templated member function calling a visitor function, and it was not related to the tuple<> loop and it did not use a lambda. In that case, I was able to work around the problem because the type in question was available explicitly as a template parameter (or more specifically as a type trait of a template parameter (i.e. T::value_type)).

That workaround is not available here.

Also, as I mentioned, in the actual code, the correct specialization is being created. So there, I also tried to check whether a function that only exists in the bar equivalent is present. I tried the code snippet below the test program, which also didn't work. And I tried to actually instantiate a foo<> object and then dynamic_cast<> its pointer to a bar pointer, which I would check for nullptr. But in that environment, I don't seem to be allowed to create the foo<> object explicitly.

Anyway, on to the code:

Code on godbolt.org

#include <iostream>
using std::cout;
using std::endl;
using std::boolalpha;
#include <string>
using std::string;
#include <tuple>
using std::make_tuple;

// template argument iteration for std::tuple<>
template<typename F, typename... Ts, std::size_t... Is>
void ForEachElement(const std::tuple<Ts...>& tup,
                    F fn,
                    std::index_sequence<Is...>)
{
    (void)(int[]) { 0, ((void)fn(std::get<Is>(tup)), 0)... };
}
template<typename F, typename...Ts>
void ForEachElement(const std::tuple<Ts...>& tup, F fn)
{
    ForEachElement(tup, fn, std::make_index_sequence<sizeof...(Ts)>());
}

template<typename T>
struct foo
{
    foo() { cout << "foo::foo<>()" << endl; }
};

struct bar
{
    bar() { cout << "bar::bar()" << endl; }
};

template<>
struct foo<float> : public bar
{
    foo() { cout << "foo::foo<float>()" << endl; }
};

template<>
struct foo<string> : public bar
{
    foo() { cout << "foo::foo<string>()" << endl; }
};


template<typename T>
inline constexpr bool is_foobar_v = std::is_base_of_v<bar, foo<T> >;

int main()
{
    int i;
    float f;
    string s;

    foo<int>();
    cout << "foo<" << typeid(int).name() << "> "
         << (is_foobar_v<int> ? "IS" : "is NOT")
         << " a child of bar" << endl;

    foo<float>();
    cout << "foo<" << typeid(float).name() << "> "
         << (is_foobar_v<float> ? "IS" : "is NOT")
         << " a child of bar" << endl;

    foo<string>();
    cout << "foo<" << typeid(string).name() << "> "
         << (is_foobar_v<string> ? "IS" : "is NOT")
         << " a child of bar" << endl;

    foo<decltype(i)>();
    cout << "foo<" << typeid(i).name() << "> "
         << (is_foobar_v<decltype(i)> ? "IS" : "is NOT")
         << " a child of bar" << endl;

    foo<decltype(f)>();
    cout << "foo<" << typeid(f).name() << "> "
         << (is_foobar_v<decltype(f)> ? "IS" : "is NOT")
         << " a child of bar" << endl;

    foo<decltype(s)>();
    cout << "foo<" << typeid(s).name() << "> "
         << (is_foobar_v<decltype(s)> ? "IS" : "is NOT")
         << " a child of bar" << endl;

    auto tup = make_tuple(i, f, s);
    ForEachElement(
        tup,
        [](const auto& elem)
        {
            foo<decltype(elem)>();
            cout << "foo<" << typeid(elem).name() << "> "
                 << (is_foobar_v<decltype((elem))> ? "IS" : "is NOT")
                 << " a child of bar" << endl;
        });
}

The output looks like this:

foo::foo<>()
foo<i> is NOT a child of bar
bar::bar()
foo::foo<float>()
foo<f> IS a child of bar
bar::bar()
foo::foo<string>()
foo<NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE> IS a child of bar
foo::foo<>()
foo<i> is NOT a child of bar
bar::bar()
foo::foo<float>()
foo<f> IS a child of bar
bar::bar()
foo::foo<string>()
foo<NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE> IS a child of bar
foo::foo<>()
foo<i> is NOT a child of bar
foo::foo<>()
foo<f> is NOT a child of bar
foo::foo<>()
foo<NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE> is NOT a child of bar

Another thing I tried:

template<typename T>
constexpr bool HasBarBaseHelper()
{ return false; }
template<>
constexpr bool HasBarBaseHelper<bar>()
{ return true; }

template<typename T>
constexpr bool HasBarBase()
{ return HasBarBaseHelper<fmt::formatter<T> >(); }

...

if (HasBarBaseHelper<foo<decltype(elem)> >())
{
...

I really don't understand why the check succeeds with decltype() of some variables, but not others. And as you can see by the output, the types appear to be exactly the same.

Any help appreciated.

Rick

0

There are 0 best solutions below