7

In a method overload situation like this:

struct A
{
  void foo( int i ) { /*...*/ }
  template<typename T> void foo( T t ) { /*...*/ }
}

How can I prevent template instantiation unless explicitly commanded?:

A a;
a.foo<int>( 1 ); // ok
a.foo<double>( 1.0 ); // ok
a.foo( 1 ); // calls non-templated method
a.foo( 1.0 ); // error

Thanks!

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
cyberguijarro
  • 715
  • 3
  • 9
  • 21

4 Answers4

10

You can introduce a depedent_type struct that prevents template argument deduction.

template <typename T>
struct dependent_type
{
    using type = T;
};

struct A
{
  void foo( int i ) { /*...*/ };
  template<typename T> void foo( typename dependent_type<T>::type t ) { /*...*/ }
}

Which in your example:

a.foo<int>( 1 );      // calls the template
a.foo<double>( 1.0 ); // calls the template
a.foo( 1 );           // calls non-templated method
a.foo( 1.0 );         // calls non-templated method (implicit conversion)

wandbox example

(This behavior is explained on cppreference > template argument deduction > non-deduced contexts.)


If you want to make a.foo( 1.0 ) a compilation error, you need to constrain the first overload:

template <typename T> 
auto foo( T ) -> std::enable_if_t<std::is_same<T, int>{}> { }

This technique makes the above overload of foo take only int arguments: implicit conversions (e.g. float to int) are not allowed. If this is not what you want, consider TemplateRex's answer.

wandbox example

(With the above constrained function, there is a curious interaction between the two overloads when a.foo<int>( 1 ) is called. I asked a question about it as I'm not sure of the underlying rules that guide it.)

Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • can be much simpler, with `foo(double)=delete`, no need for enable_if and the non-deduced wrapper, see my anwer :) – TemplateRex Jan 13 '17 at 14:30
  • This seems to be the best option for me. Another way to prevent `a.foo( 1.0 )` with less compiler compliance requirements is to do something like `static_assert( std::is_same, "..." );`. – cyberguijarro Jan 16 '17 at 14:47
  • Why doesnt Cpp directly explicit specialization after implicit specialization? This goes against the rule of seperate declaration from implementation. There is no reason why implicit instantiation before explicit definition should be an error! Cpp shoudl do overload resolution. –  Feb 29 '20 at 19:38
1

By far the simplest way to do what you want is to explicitly delete the overload you don't want:

void foo(double) = delete;

I.e. to have the following explicit example:

#include <iostream>

struct A
{
    void foo(int) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    void foo(double) = delete;

    template<typename T> 
    void foo( T ) {std::cout << __PRETTY_FUNCTION__ << "\n"; }
};

int main() 
{    
    A a;
    a.foo<int>( 1 );        // ok
    a.foo<double>( 1.0 );   // ok
    a.foo( 1 );             // calls non-templated method
    a.foo( 1.0 );           // error    
}

With the last line in main commented out this prints

void A::foo(T) [with T = int]
void A::foo(T) [with T = double]
void A::foo(int)

and with the last line left in this prints

prog.cc: In function 'int main()':
prog.cc:18:16: error: use of deleted function 'void A::foo(double)'
     a.foo( 1.0 );           // error
                ^
prog.cc:6:10: note: declared here
     void foo(double) = delete;
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Note that this approach allows `foo` to be called with a `float`, `char` *(and all types implicitly convertible to `int`)*. Not sure if this is what the OP wants. – Vittorio Romeo Jan 13 '17 at 15:49
  • @VittorioRomeo sure, but the same applies to your solution: does he want exactly `int`, or only non-floating, or only non-narrowing conversions, etc. In any case, the `=delete` solution can be expanded to exclude (but not remove, because you want an error) other overloads. – TemplateRex Jan 13 '17 at 16:11
  • Yes, I'm not criticizing your answer - just saying that you should probably mention it. I'll update my answer to make it clear that implicit conversions are not accepted. – Vittorio Romeo Jan 13 '17 at 16:25
  • Interesting, although not exactly what I need. I would need to `= delete` `void foo(int)` to prevent templated instantiation of `void foo(int)`, but I want void foo(int) to be still accessible. – cyberguijarro Jan 16 '17 at 14:30
  • @cyberguijarro I am not sure what you mean: doing `foo(double)=delete;` still allows the template `foo` to be instantiated (as my example clearly shows). What it does is first add an overload `foo(double)`, and then during overload resolution of `foo(1.0)` it will select `foo(double)` and then it will find out that this overload has been deleted. – TemplateRex Jan 16 '17 at 19:46
0

Adding another suggestion to the pot, and in a similar vein as Vittorio's answer you can also add another template parameter to the signature:

template <class UserType, class InputType>
void foo(InputType x){...}

Then to use it you must specify the first template parameter as it cannot be deduced. This has the slight advantage of being able to distinguish between what the user wanted and what was passed in, which could possibly be of use in some cases.

SirGuy
  • 10,660
  • 2
  • 36
  • 66
0

Halelulah!!!!!!! I finally found the answer after years of grief. Cause I knew that cpp rule, seperate declaration from definition was not just thrown in the dump!!!

// declare template
template <class T> void test();

// declare specialization
template <> // very import to start with this token
void<int> test();

// implicit instantiation
template // if you use this instead, you get instantiation
void<int> test();

I still wonder why this is necessary. Probably it is an optimization from those wise people of the Cpp Committee.