简体   繁体   中英

Overloaded templated function declaration order

I have some code that reads like this SSCCE:

#include <map>
#include <string>
#include <functional>

using std::map;
using std::string;
using std::function;
using std::forward;

template<typename ToType, typename... FromTypes>
ToType construct(FromTypes&&... fromTypes) {
  return ToType(forward<FromTypes>(fromTypes)...);
}

class Variant {
public:
  map<string, Variant> toMap() const;
};

map<string, Variant> Variant::toMap() const {
  return {
      {"test", {}},
      {"test1", {}},
    };
}

// #1
template <typename MapType>
MapType variantToMap(Variant const& v) {
  // I expect an error on the following line because it's referencing the function labeled #4 which has not been declared yet.
  return variantToMap<MapType>(v, construct<typename MapType::key_type, string>, construct<typename MapType::mapped_type, Variant>);
}

// #2
template <typename MapType>
MapType variantToMap(Variant const& v, function<typename MapType::key_type(string)> const& keyConvert) {
  // I expect an error on the following line because it's referencing the function labeled #4 which has not been declared yet.
  return variantToMap<MapType>(v, keyConvert, construct<typename MapType::mapped_type, Variant>);
}

// #3
template <typename MapType>
MapType variantToMap(Variant const& v, function<typename MapType::mapped_type(Variant)> const& valueConvert) {
  // I expect an error on the following line because it's referencing the function labeled #4 which has not been declared yet.
  return variantToMap<MapType>(v, construct<typename MapType::key_type, string>, valueConvert);
}

// #4
template <typename MapType>
MapType variantToMap(Variant const& v, function<typename MapType::key_type(string)> const& keyConvert, function<typename MapType::mapped_type(Variant)> const& valueConvert) {
  MapType res;
  for (auto pair : v.toMap()) {
    res[keyConvert(pair.first)] = valueConvert(pair.second);
  }

  return res;
}

int main() {
  Variant a;

  // #1
  variantToMap<map<string, Variant>>(a);

  // #2
  {
    int counter = 0;
    variantToMap<map<int, Variant>>(a, [&counter](string) -> int { return ++counter; });
  }

  // #3
  variantToMap<map<string, int>>(a, [](Variant) -> int { return 42; });

  // #4
  {
    int counter = 0;
    variantToMap<map<int, int>>(a, [&counter](string) -> int { return ++counter; }, [](Variant) -> int { return 42; });
  }

  return 0;
}

Which compiles and works fine.

However, because it's declared out of order, I would expected this to have thrown errors. I assume that the reason why it's not is related to the fact that these functions are all templated. However, this is still a bit confusing to me because (for instance) the call to variantToMap #4 within variantToMap #1 has 3 arguments, which means

Is Clang and GCC inappropriately accepting these functions, or is this allowed? And if it is allowed, what's the rationale for it?

It should be noted that I already have reordered these functions, I'm just curious why it worked in the first place.

C++ uses two phase name lookup to resolve identifiers found in template definitions. (Unless you have MSVC, which uses the second phase only).

When the template is declared/defined, all names which do not depend on the template arguments are resolved. When it is instantiated, names that do depend on template arguments are resolved. This time, names visible at the point of instantiation participate and can be found during lookup.

In your case the inner variantToMap calls depend on the template arguments, so they are not resolved until the outer functions are called. At that point all 4 variants are visible and can be found.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM