2

I have a case where I need to have a forwarding function defined before a template base function is defined/declared. However, if I call the forwarding function (fwd) that in turn calls the base function test, it says that the base template function is not visible (see the code below). However, if test is called directly, everything works.

So my question is this, is it possible to have the forwarding function make a call to a base template function that is defined later in the compilation unit (before it is used but after the forwarding function)? If not, do I have any options to work around this? I would like to avoid a forward declaration before fwd as that would make use of the library I am developing harder. I think if I could force fwd to be inline it would solve the problem but I have no way of doing that unless a macro is used.

#include <iostream>
#include <vector>

template<typename T, std::enable_if_t<std::is_scalar<T>::value, int> = 0>
void test(const T& t)
{
  std::cout << "Scalar"  << std::endl;
}

template<typename T>
void fwd(T&& t)
{
  test(std::forward<T>(t));
}

template<typename T>
void test(const std::vector<std::vector<T>>& t)
{
  std::cout << "vector vector of T" << std::endl;
}

int main(int argc, const char * argv[]) {

  test(1);  //OK, prints Scalar
  fwd(1);   //OK, prints Scalar

  test(std::vector<std::vector<int>>()); //OK, prints vector vector of T

  // Causes compile error: Call to function 'test' that is neither visible in the template definition
  // nor found by argument dependent lookup
  fwd(std::vector<std::vector<int>>());

  return 0;
}
dr_pepper
  • 1,587
  • 13
  • 28

1 Answers1

4

The name test in fwd is a dependent name. It will be resolved into two steps:

  1. Non-ADL lookup examines function declarations ... that are visible from the template definition context.
  2. ADL examines function declarations ... that are visible from either the template definition context or the template instantiation context.

Given that the relative order of test and fwd should not be changed, one possible solution is to use a fake tag struct in the namespace to activate ADL:

namespace my_namespace
{
    struct Tag {};

    template<typename T, std::enable_if_t<std::is_scalar<T>::value, int> = 0>
    void test(const T& t, Tag = Tag{}) {
        std::cout << "Scalar" << std::endl;
    }

    template<typename T> 
    void fwd(T&& t) {
        test(std::forward<T>(t), Tag{});
    }

    template<typename T>
    void test(const std::vector<std::vector<T>>& t, Tag = Tag{}) {
        std::cout << "vector vector of T" << std::endl;
    }
}

int main() {
    my_namespace::test(std::vector<std::vector<int>>()); // OK
    my_namespace::fwd(std::vector<std::vector<int>>());  // OK, too
}

Demo


Depending on what test overloads you have, another solution might be to wrap these functions into structs and use template specialization instead of function overloading:

template<class T>
struct Test {
    static void op(const T& t) {
        std::cout << "Scalar" << std::endl;
    }
};

template<typename T>
void fwd(T&& t) {
    Test<std::decay_t<T>>::op(std::forward<T>(t));
}

template<class T>
struct Test<std::vector<std::vector<T>>> {
    static void op(const std::vector<std::vector<T>>& t) {
        std::cout << "vector vector of T" << std::endl;
    }
};

int main() {
    fwd(1);
    fwd(std::vector<std::vector<int>>());
}

Demo

Evg
  • 25,259
  • 5
  • 41
  • 83
  • For the first solution, are you relying on this rule from the dependent name link to activate ADL? "To make ADL examine a user-defined namespace, either std::vector should be replaced by a user-defined class or its element type should be a user-defined class" – dr_pepper Jan 04 '20 at 18:55
  • The first solution works better for me as its handy to have a template function that handles all of the scalars. Once i use structs I start getting into ambiguity issues when combining those with the enable_if calls. – dr_pepper Jan 04 '20 at 19:17
  • @dr_pepper "...or a dummy function argument can be added". I rely on the fact that the namespace that contains `Tag` will be examined. If you have another argument that is contained in that namespace (and in your example you don't), there is no need for `Tag`. With structs you can also use [`void_t` trick](https://stackoverflow.com/questions/27687389/how-does-void-t-work). – Evg Jan 04 '20 at 19:27