0

I have a template function which recurses over a parameter pack. In essence it is meant to map something.Get<A,B,C>() into something.Get<A>().Get<B>().Get<C>().

This can be achieved by doing (full stand-alone source below the fold)

template <typename... Pack> class Struct {
  std::tuple<Pack...> mTuple;
  template <typename Type> auto &GetRef_internal() {
    return std::get<first_true<std::is_same<Type, Pack>::value...>::value>(
        mTuple);
  }
public:
  template <typename Current> Current &GetRef() {
    return GetRef_internal<Current>();
  }
  template <typename Current, typename... Next,
            typename std::enable_if<sizeof...(Next) != 0>::type * = nullptr>
  auto &GetRef() {
    auto current = GetRef_internal<Current>();
    return current.GetRef<Next...>();
  }
};

where first_true returns the index of the first element which is true.

This compiles with g++, and seemingly on MSVC too using an online compiler. When compiling with clang++ I get the following error though:

test.cxx:40:31: error: expected '(' for function-style cast or type construction
    return current.GetRef<Next...>();
                          ~~~~^
test.cxx:38:9: error: cannot deduce return type 'auto &' for function with no return statements
  auto &GetRef() {
        ^
test.cxx:48:12: note: in instantiation of function template specialization 'Struct<Struct<int, Struct<float, float> >, Struct<char>, int>::GetRef<Struct<int, Struct<float, float> >, Struct<float, float> , nullptr>' requested here
          .GetRef<Struct<int, Struct<float, float>>, Struct<float, float>>();
           ^
2 errors generated.

What could cause this?

p.s. The actual 'production code' is more useful than the example makes it seem, but that would be too much to post here.

=========================================================================

#include <tuple>
#include <type_traits>

// Template to check if condition holds true for all members of a parameter
// pack.
template <bool... b> struct BoolArray {};
template <bool... b>
using all_true = std::is_same<BoolArray<b...>, BoolArray<(b, true)...>>;

//helper type trait
template <bool... b> struct first_true {
  template <
      unsigned index = 0,
      typename std::enable_if<index<sizeof...(b)-1>::type * =
                                  nullptr> static constexpr unsigned check() {
    return std::get<index>(std::make_tuple(b...)) ? index : check<index + 1>();
  }
  template <unsigned index = 0,
            typename std::enable_if<index >= sizeof...(b)-1>::type * = nullptr>
  static constexpr unsigned check() {
    return std::get<index>(std::make_tuple(b...)) ? index : 0;
  }
  static constexpr unsigned value = first_true<b...>::check();
};

//The actual problem struct
template <typename... Pack> class Struct {
  std::tuple<Pack...> mTuple;
  template <typename Type> auto &GetRef_internal() {
    return std::get<first_true<std::is_same<Type, Pack>::value...>::value>(
        mTuple);
  }
public:
  template <typename Current> Current &GetRef() {
    return GetRef_internal<Current>();
  }
  template <typename Current, typename... Next,
            typename std::enable_if<sizeof...(Next) != 0>::type * = nullptr>
  auto &GetRef() {
    auto current = GetRef_internal<Current>();
    return current.GetRef<Next...>();
  }
};

int main() {
  // Define a random nested struct
  Struct<Struct<int, Struct<float, float>>, Struct<char>, int> myStruct;
  // Then retrieve one of the substructures to instantiate the template
  auto substruct =
      myStruct
          .GetRef<Struct<int, Struct<float, float>>, Struct<float, float>>();
 return 0;
}
Coding Cat
  • 13
  • 4

1 Answers1

0

current.GetRef<Next...> is a dependent name, so you need to specify that GetRef names a template using the template keyword:

return current.template GetRef<Next...>();

See Where and why do I have to put the "template" and "typename" keywords? for more information about dependent names.

Community
  • 1
  • 1
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • This worked! learned something new today. Will accept this answer as soon as it lets me. Interesting that g++ resolves it even with -pedantic. – Coding Cat Feb 10 '17 at 14:13
  • @CodingCat the spec says if you have "foo.bar<", to know whether "bar" is a template, it should first be looked up in the class of "foo". And if "bar" is not found there, it should be looked up in the context of the entire expression. But to know whether or not it is present in the class of the object, it must first wait until the surrounding template is instantiated. Clang does so, apparently. GCC does not, hence thinks that it's a template. This is also the issue in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55576 , I think. In that bugreport, GCC rejects a valid program. – Johannes Schaub - litb Feb 11 '17 at 16:41
  • The entire purpose of the "template" disambiguator is to help the compiler decide whether or not the name is a template. But GCC decides to do this on its own, without using the disambiguator, and therefore finds the "GetRef" in the context of the expression as a member. I don't think that this GCC behavior is correct. – Johannes Schaub - litb Feb 11 '17 at 16:43
  • I don't have a local installation of MSVC but I linked to an online compiler in my post which seems to use it, and it to accepts my test program hence why I originally assumed Clang was in the wrong. – Coding Cat Feb 12 '17 at 20:58