41

I have the following code that compiles and works well:

template<typename T>
T GetGlobal(const char *name);

template<>
int GetGlobal<int>(const char *name);

template<>
double GetGlobal<double>(const char *name);

However I want to remove the "default" function. That is, I want to make all calls to GetGlobal<t> where 't' is not an int or a double an error.

For example, GetGlobal<char>() should be a compile time error.

I tried to just delete the default function, but, as I imagined, I received a lot of errors.. So is there a way to "disable" it and allow calls only to the specialized versions of the function?

Thanks!

5 Answers5

40

Though it is an old and outdated question, it may worth noting that C++11 had solved this issue using deleted functions:

template<typename T>
T GetGlobal(const char *name) = delete;

template<>
int GetGlobal<int>(const char *name);

UPDATE

This will not compile under MacOS llvm 8. It is due to a still hanging 4 years old defect (see this bug report).

The following workaround will fit the issue (using a static_assert construct).

template<typename T>
T GetGlobal(const char *name) {
    static_assert(sizeof(T) == 0, "Only specializations of GetGlobal can be used");
}

template<>
int GetGlobal<int>(const char *name);

UPDATE

Visual studio 15.9 has the same bug. Use the previous workaround for it.

HappyCactus
  • 1,935
  • 16
  • 24
  • This is the best, thanks! It might also be relevant here: https://stackoverflow.com/questions/12629191/compile-time-error-for-non-instantiated-template-members-instead-of-link-time-er – egpbos Aug 03 '17 at 08:16
  • llvm 5.0 and 3.9 have fixed this bug. – Roy Falk Jun 13 '20 at 08:36
32

To get a compile-time error implement it as:

template<typename T>
T GetGlobal(const char *name) { T::unimplemented_function; }
// `unimplemented_function` identifier should be undefined

If you use Boost you could make it more elegant:

template<typename T>
T GetGlobal(const char *name) { BOOST_STATIC_ASSERT(sizeof(T) == 0); }

C++ Standard guarantees that there is no such type which has sizeof equal to 0, so you'll get a compile-time error.

As sbi suggested in his comments the last could be reduced to:

template<typename T>
T GetGlobal(const char *name) { char X[!sizeof(T)]; }

I prefer the first solution, because it gives more clear error message (at least in Visual C++) than the others.

Community
  • 1
  • 1
Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
  • 4
    In order for the first case to fail at _compile-time_, Koper would need an _undeclared_ function, not an _undefined_ one. And the only way to get this to compile is to make it dependent on `T`. Something like `T::some_thing_that_is_definitely_undeclared` might do. (The second one could probably be mimicked with `char dummy[!sizeof(T)];`.) – sbi Oct 27 '09 at 08:43
  • 1
    Using `unimplemented_function` gives more clear error message (at least in Visual C++) than using `T::unimplemented_function`. – Kirill V. Lyadvinsky Oct 27 '09 at 08:55
  • 1
    @Kirill: Using `unimplemented_function` won't work in a compiler that does two-phase lookup. (Currently, VC doesn't do this.) Such a compiler won't compile the template _definition_, even though it's never instantiated. – sbi Oct 27 '09 at 10:27
  • -1: SBI is correct. The identifier is looked up and as it is not a fucntion call with a dependent argument (ie. an ADL call) then it will be an error. Please fix the example - and get your point back! :) – Richard Corden Oct 27 '09 at 12:09
  • Compilers are allowed to moan for `char X[!sizeof(T)];` too. But as far as i know, no one does really. The reason is that the Standard allows them to reject any template definitions for which never a valid specialization can be instantiated, regardless of the template arguments. This is the case here. Most implementations however "behave" and don't error out at the definition. So i'll +1 you xD Small tip: `BOOST_MPL_ASSERT` produces far better "error messages" than `BOOST_STATIC_ASSERT`. – Johannes Schaub - litb Oct 27 '09 at 14:47
  • 2
    Something like this `template struct not_defined : mpl::false_ { }; template T GetGlobal(char const *name) { BOOST_MPL_ASSERT(( not_defined )); }` will give some nice error: http://codepad.org/rq30wdeq . – Johannes Schaub - litb Oct 27 '09 at 14:53
  • In C++11 you can use `static_assert` instead of `BOOST_STATIC_ASSERT`. – sorosh_sabz May 29 '17 at 03:26
  • something's unclear to me in this post: is there a specific reason to use BOOST_STATIC_ASSERT(sizeof(T) == 0) instead of simply BOOST_STATIC_ASSERT(false) ? – 4126cfbe1fd5 Jan 22 '20 at 14:02
6

If you don't implement it, you'll at least get a linker error. If you want a compile-time error, you could do this with class templates:

template<typename T>
struct GlobalGetter;

template<>
struct GlobalGetter<int> {
  static int GetGlobal(const char *name);
};

template<>
struct GlobalGetter<double> {
  static double GetGlobal(const char *name);
};

template<typename T>
T GetGlobal(const char *name)
{
  return GlobalGetter<T>::GetGlobal(name);
}
sbi
  • 219,715
  • 46
  • 258
  • 445
  • Any idea _why_ a template function forward declaration doesn't yield a compiler error, while a template class forward declaration does? – xtofl Oct 27 '09 at 08:44
  • Too much writings if you have much more specializations than for 'int' and 'double'. – Kirill V. Lyadvinsky Oct 27 '09 at 08:57
  • 2
    What is `GlobalGetter(name)`? Constructor? There is no constructor with single argument in `GlobalGetter` and `GlobalGetter`. – Alexey Malistov Oct 27 '09 at 09:09
  • 1
    @xtofl: Because a function template declaration is all that you need to compile calls to a function. (Whereas for classes you need the definition.) – sbi Oct 27 '09 at 10:25
  • @Kiril: Instead of writing a function template specialization, you write a specialization for a class template which only contains one stat function. Yes, it's a few more keystrokes to type, but it's not that bad. However, your second solution (the one using `STATIC_ASSRT` I like, too. – sbi Oct 27 '09 at 10:29
3

I would suggest not to actually provide an implementation, just a bare declaration of the method.

The other option would be to use a compile-time assert. Boost has a number of such beasts.

namespace mpl = boost::mpl;
BOOST_MPL_ASSERT((mpl::or_< boost::same_type<T, double>,
                            boost::same_type<T, int> >));

There is also its message version counterpart, which would help.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
2

The following are alternative techniques to using boost:

Declare a typedef to a dependent name

This works because name lookup for DONT only occurs when 'T' has been replaced. This is a similar (but legal) version of the example given by Kirill

template <typename T>
T GetGlobal (const char * name) {
    typedef typename T::DONT CALL_THIS_FUNCTION;
}

Use an incomplete return type

This technique doesn't work for specializations, but it will work for overloads. The idea is that its legal to declare a function which returns an incomplete type, but not to call it:

template <typename T>
class DONT_CALL_THIS_FUNCTION GetGlobal (const char * name);
Community
  • 1
  • 1
Richard Corden
  • 21,389
  • 8
  • 58
  • 85