9

Is there a better way to do the following?

#include <iostream>

template <typename T>
T Bar();

template <>
int Bar<int>() { return 3; }

// Potentially other specialisations

int main()
{
    std::cout << Bar<int>() << std::endl; // This should work
    std::cout << Bar<float>() << std::endl; // This should fail
}

The problem with this solution is that it fails at (understandably) link time with "undefined reference to float Bar<float>()" or the like. This can be confusing for other developers as they may suspect an implementation file is not being linked.

I do know another potential solution:

template <typename T>
T Bar() { BOOST_STATIC_ASSERT(sizeof(T) == 0); }

This causes a compiler error when Bar<float>() is requested, exactly what I want. However, I'm concerned that technically a compiler may reject this just as gcc rejects BOOST_STATIC_ASSERT(false) because it knows that it will fail regardless of the template parameter, since sizeof(T) can never be zero.

In summary, I want to know whether:

  1. There is another way to do this.
  2. I'm mistaken and BOOST_STATIC_ASSERT(sizeof(T)) actually can't fail without instantiation.
  3. The only way is to let this be a linker error as above.
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
voltrevo
  • 9,870
  • 3
  • 28
  • 33

5 Answers5

6

This could work:

template <typename T>
T Bar() {
  T::ERROR_invalid_template_argument_;
}

template <>
int Bar<int>() { return 3; }

You could also use the highest size possible if you're afraid of using 0:

  static_assert(sizeof(T) == -1, "No specialization");
Pubby
  • 51,882
  • 13
  • 139
  • 180
  • +1 -- but that seems a reasonable name for someone to use when they're writing a class template. Perhaps put it in ALL CAPS in order to make that less likely. – Billy ONeal Nov 29 '11 at 03:29
  • Yeah, it's not ideal, but I do like this. – voltrevo Nov 29 '11 at 03:48
  • Not sure what you meant by "highest size possible" since you followed that with sizeof(T) == -1, but sizeof(T) == numeric_limits::max is an interesting idea. – voltrevo Nov 29 '11 at 03:58
  • 2
    @Mozza314: Because the type of `sizeof` is `std::size_t` (an unsigned integer type), -1 will be promoted to an unsigned type (using modular arithmetic, which guarantees a maximum value). That said, just use zero. At some point you have to trust the compiler is doing The Right Thing. – GManNickG Nov 29 '11 at 04:05
  • Yeah I wrote that comment before you edited your answer GMan. However, std::size_t(-1) is undefined, no? Even though of course practically speaking with twos complement you'll get the maximum value. – voltrevo Nov 29 '11 at 04:21
  • @Mozza314: No, signed to unsigned is specified. Casting -1 to `unsigned T` is guaranteed to be the maximum value of `unsigned T`. – GManNickG Nov 29 '11 at 04:53
4

BOOST_STATIC_ASSERT(sizeof(T) == 0); isn't allowed to fail until the template is instantiated, so I would just do that one. You are correct that BOOST_STATIC_ASSERT(false); triggers each time.


The reason for this has to do with two-phase name lookup. This is, essentially, the following: when a template is compiled, it's compiled twice. The first time a compielr sees a template it compiles everything except the expressions dependent on template parameters, and the second compilation happens once the template parameter is known, compiling the instantiation fully.

This is why BOOST_STATIC_ASSERT(false); will fail always: nothing here is dependent and the assert is processed immediately, as if the function weren't a template at all. (Note that MSVC does not implement two-phase look-up, so this fails at instantiation, incorrectly.) Contrarily, because T is dependent (§14.6.2.1), BOOST_STATIC_ASSERT(sizeof(T) == 0); is dependent, and is not allowed to be checked until the template is instantiated. (Where upon it will always fail.)

If a compiler tries to be thoughtful and fail it ahead of time, it would be non-conforming. You're suppose to be able to rely on this stuff. That said, if fear gets the best of you it's trivial to really make it wait:

BOOST_STATIC_ASSERT(sizeof(typename T::please_use_specializations) == 0);

This is both guaranteed to fail, and impossible for a compiler to correctly "smartly" fail ahead of time.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Can you quote from the standard? My suspicion is that the compiler is allowed to give an error if it can tell there is no template parameter that would pass. This post from the boost mailing list seems to indicate this: http://lists.boost.org/Archives/boost/2004/06/66983.php – voltrevo Nov 29 '11 at 03:45
  • @Mozza314: I'm editing my answer, I'm certain that poster is wrong. Give me a minute. – GManNickG Nov 29 '11 at 03:56
  • You are awesome :-D. Thankyou. – voltrevo Nov 29 '11 at 04:08
  • "BOOST_STATIC_ASSERT(sizeof(T) == 0); is dependent, and is not allowed to be checked until the template is instantiated. (Where upon it will always fail.)" that is false. The Standard allows compilers to reject templates that always fail when instantiated. If a compiler tries to be thoughtful and fails it ahead of time it is clever. What if instead of `0` you wanted to write `o` - that clever compiler will have shown you your typo before you get into a messy instantiation backtrace. – Johannes Schaub - litb Jan 28 '12 at 19:30
  • @JohannesSchaub-litb: I don't think it is allowed to do that, is there a place I can read on it? – GManNickG Jan 28 '12 at 19:51
  • you can read on the spec, 14.6p7. It is "ill-formed; no diagnostic required", like the boost mailing list post says. Or you can raise an SO question if you need detailed guidance. – Johannes Schaub - litb Jan 28 '12 at 19:57
  • @JohannesSchaub-litb: Huh, I suppose you're right now that I look at it from the other side. So the solution at the bottom of my answer should work, then? (I suppose in principle it could also know no type has that inner type, though.) – GManNickG Jan 29 '12 at 04:57
  • @GMan yes that should work regarding the inner type, but you still compare against `==0`. If you would compare against `==1`, I don't think there is a way to diagnose it for the compiler. For knowing that no type has that inner type it would need to be a link time optimizing compiler. But then it has already compiled the code, so giving a diagnostic at linking saying "this and that template is ill-formed" would seem to be very weird xD I think the easiest is simply using `always_false::value`, with `always_false` being derived from `false_type`. – Johannes Schaub - litb Jan 29 '12 at 22:07
  • @JohannesSchaub-litb: Oh right haha. Yeah that does seem best. I'll edit my answer at some point. – GManNickG Jan 30 '12 at 04:09
0

You could do something like the following:

template <typename T>
T Bar()
{ T::unspecialized_method_called; }

This of course assumes, that T doesn't have a member with the given name, so you would have to choose your "error message" accordingly (e.g. by violating naming conventions).

Grizzly
  • 19,595
  • 4
  • 60
  • 78
0

use static_assert with c++0x

template <typename T> 
void bar(){
 static_assert(false, " invalid argument type");
}

this will raise an error when compiling.

For c++ 98/2003, we could try this

template <typename T> 
void bar(){
char invalid_arg_[0];
}

array at lest contains one element. So the compiler would complain. But the error message might be useless to show what happened.

The first wouldn't be a choice since it always fails.

BruceAdi
  • 1,949
  • 1
  • 11
  • 8
  • 2
    First is incorrect as it will always fail. Second compiles fine under GCC without -pedantic. – Pubby Nov 29 '11 at 03:48
  • 1
    Interesting. I'm guessing it's no better than BOOST_STATIC_ASSERT(sizeof(T) == 0) though. This solution has the same problem. – voltrevo Nov 29 '11 at 03:53
  • 1
    Pubby's comment went through before mine. Yeah -pedantic fails even without instantiation. – voltrevo Nov 29 '11 at 03:54
0

There's a little caveat if you're using gcc without -pedantic, in which case it is possible to have sizeof(T) == 0 - when T is a zero-length array.

#include <iostream>

#include "boost/static_assert.hpp"

template <typename T>
void Foo()
{
    BOOST_STATIC_ASSERT(sizeof(T) == 0);
    std::cout << "Actually, it is possible to instantiate this." << std::endl;
}

int main()
{
    Foo<int[0]>();

    return 0;
}

In this case you can work around it by using this instead:

BOOST_STATIC_ASSERT(sizeof(T) == sizeof(T) + 1);

It may be better to encapsulate this trick, which can improve readability because it expresses your intent:

#define NEVER_INSTANTIATE(T) BOOST_STATIC_ASSERT(sizeof(T) == sizeof(T) + 1);

As GMan explained, this can't fail without instantiation just like sizeof(T) == 0. However, the moral of this story should probably instead be always compile with -pedantic instead.

voltrevo
  • 9,870
  • 3
  • 28
  • 33