0

The following code works:

struct A
{
   int v = 3;
};

namespace Foo
{
   template <int k=11>
   int operator+(A const& lhs, A const& rhs)
   {
      return lhs.v + rhs.v + k;
   }
}

using Foo::operator+;

int main()
{
   A a1, a2;
   std::cout << a1 + a2 << std::endl;
   return 0;
}

The using Foo::operator+; directive brings Foo::operator+ into the external lookup scope and the when operator+ is used in the cout call, the default template value of 11 is taken and the result is, as expected: 17 (=3+3+11).

My question is how to change the using clause to explicitly instantiate the operator+ function with a non-default template value?

The line using Foo::operator+<42> does not work.
This is due to ISO C++ Standard 7.3.3.5: A using-declaration shall not name a template-id.
Is there a way around this?

bogdan
  • 9,229
  • 2
  • 33
  • 48
Adi Shavit
  • 16,743
  • 5
  • 67
  • 137
  • Operators don't "play well" with explicit template instantiation - they play best with implicit deduction. Functions are probably a better idea. – Niall Apr 28 '16 at 08:20
  • @Niall: Is there any way around this? The limitation in this case seems to come from the limitation on the `using` declaration (as-per §7.3.3.5). – Adi Shavit Apr 28 '16 at 08:23
  • Not really, I would look to use functions and factor the policies out an tie them to the type or accept them as an argument to the function (again allow for implicit deduction - it generally results in a "neater" syntax in cases such as these). – Niall Apr 28 '16 at 08:26
  • At a stretch you can try to inline a locally overloaded operator for those types; `int operator+(A const& rhs, A const& lhs) { return Foo::operator+<42>(rhs, lhs); }` but this can quickly become cumbersome. – Niall Apr 28 '16 at 08:30
  • @Niall: Yeah it would be cumbersome. However, I think I nailed it (isn't C++ great in letting you override its own constraints). I'll post my solution below after a run a few more tests. I simulate templated-namespaces using struct templates with static methods. – Adi Shavit Apr 28 '16 at 08:41

2 Answers2

2

Answering myself...
The problem seems to stem from ISO C++ Standard 7.3.3.5:

A using-declaration shall not name a template-id.

This prevents the acceptance of: using Foo::operator+<42>.

As a work-around I found the following solution that does what I need, at the cost of an extra namespace redirection. The code may still need some massaging but it does get the task done with minimal duplication on the user's side.

See a working version here.

struct A
{
   int v = 0;
};

template <int k>
struct Bar
{
   static int plus(A const& lhs, A const& rhs)
   {
      return rhs.v + lhs.v + k;
   }
};
    
namespace Boo
{
   using Baz = Bar<42>; // same as `typedef Bar<42> Baz;`

    //#include "foo_operators.h"
    namespace Foo
    {
       int operator+(A const& rhs, A const& lhs)
       {
          return Baz::plus(lhs, rhs);
       }
    }
}

namespace Goo
{
   using Baz = Bar<3>; 

    //#include "foo_operators.h"
    namespace Foo
    {
       int operator+(A const& rhs, A const& lhs)
       {
          return Baz::plus(lhs, rhs);
       }
    }
}


using namespace std;
int main()
{
   {
      using Boo::Foo::operator+;
      A a1, a2;
      cout << a1 + a2 << endl;
   }

   {
      using Goo::Foo::operator+;
      A a1, a2;
      cout << a1 + a2 << endl;
   }

   return EXIT_SUCCESS;
}

// In real code extract to foo_operators.h: the partial file snippets to get #included multiple times
// namespace Foo
// {
//    int operator+(A const& rhs, A const& lhs)
//    {
//       return Baz::plus(lhs, rhs);
//    }
// }

The idea is to replace the Foo namespace with a struct template with static methods Bar.
This allows instantiating the Foo type using the desired params.
The operators just call the static methods via the externally defined and parametrized type.
ADL takes care of the rest.

In the example above, the user created 2 new namespaces, Boo and Goo to have 2 different parametrizations of the plus operator. Finally, at the point of usage the user brings in the desired version of the operator+ with the using directive.

In this approach there doesn't seem to be an option for specifying default parameter values.

In real code the operators themselves would be stored in a snippet file to be #includeed into the code after the declaration of the parameterized type (Baz in the example).

Take 2

Here's a much cleaner version that uses a simple templated traits class and avoid both the extra namespace and the operator redirection function plus.

template <int k>
struct op_traits_t
{
   static const int K = k;
};


namespace Boo
{
   using op_traits = op_traits_t<42>; // same as `typedef op_traits_t<42> op_traits;`
   //#include "foo_operators.h"
    // this is a partial file snippet
    int operator+(A const& rhs, A const& lhs)
    {
       return rhs.v + lhs.v + op_traits::K; 
    }
}

namespace Goo
{
   using op_traits = op_traits_t<3>;
   //#include "foo_operators.h"
    // this is a partial file snippet
    int operator+(A const& rhs, A const& lhs)
    {
       return rhs.v + lhs.v + op_traits::K; 
    }
}

int main()
{
   {
      using Boo::operator+;
      A a1, a2;
      cout << a1 + a2 << endl;
   }

   {
      using namespace Goo;
      A a1, a2;
      cout << a1 + a2 << endl;
   }

   return EXIT_SUCCESS;
}
Community
  • 1
  • 1
Adi Shavit
  • 16,743
  • 5
  • 67
  • 137
1
std::cout << operator+<12>(a1, a2) << std::endl;

But don't do this. Operator + should behave in an unsurprising way.

Use a named function:

namespace Foo
{
   template <int k=11>
   int add_plus_k(A const& lhs, A const& rhs)
   {
      return lhs.v + rhs.v + k;
   }
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Obviously, this is a contrived example. The operator in my actual code is unsurprising. I want to keep the original `cout` line unchanged and only change the way the operator function behaves when brought into scope. – Adi Shavit Apr 27 '16 at 15:42
  • Essentially what I am trying to achieve a policy-based design for operators where the template parameter k is a policy-related value. – Adi Shavit Apr 28 '16 at 07:01
  • 1
    @AdiShavit if it were me I would have one operator that defers to a policy-based function – Richard Hodges Apr 28 '16 at 13:56