Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do C++ templates match if method doesn't type-check?

The follow code does not compile because struct A doesn't support the -- operator.

struct A {};

struct B {
  void Run() {}
  A& Dec(A& a) { return --a; }
};

int main(int argc, char** argv) {
  B b;
  b.Run();
}

Same with this code.

struct A {};

template <class T>
struct B {
  void Run() {}
  A& Dec(A& a) { return --a; }
};

int main(int argc, char** argv) {
  B<A> b;
  b.Run();
}

So why does this compile (in C++11)?

struct A {};

template <class T>
struct B {
  void Run() {}
  T& Dec(T& a) { return --a; }
};

int main(int argc, char** argv) {
  B<A> b;
  b.Run();
}

It appears that instantiating the template does not automatically instantiate unused methods within the template that depend on the type parameter to type-check, which means that the template will match even if some of its methods don't. It's disappointing because I was hoping to use SFINAE to detect the applicability of various methods and operators to types, but if template substitution succeeds even when calls to the methods would be compile-time errors, the technique won't work.

like image 220
Joel Micah Donovan Avatar asked Jul 17 '17 17:07

Joel Micah Donovan


People also ask

How will you restrict the template for a specific datatype?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

What are disadvantages of using templates in C++?

There are three primary drawbacks to the use of templates. First, many compilers historically have very poor support for templates, so the use of templates can make code somewhat less portable. Second, almost all compilers produce confusing, unhelpful error messages when errors are detected in template code.

Why do we use templates in C?

Templates are an important part of C++, as already mentioned, they allow you to develop functions or Classes that are type generic. You specify the type when you use them. You really should learn templates if, for no other reason, to understand boost and the standard template libraries.

What is the rule of compiler by templates?

The compiler usually instantiates members of template classes independently of other members, so that the compiler instantiates only members that are used within the program. Methods written solely for use through a debugger will therefore not normally be instantiated.


1 Answers

It was decided (by the C++ committee) that methods of template classes would only have their bodies instantiated if they where used.

This makes it easier to write some C++ code at the cost of hard errors when they are used.

As an example of this, std::vector uses it with std::vector::operator<; if you don't have a < calling it is an error. If you do, calling it works.

More modern C++ would encourage SFINAE disabling it so you can detect if < is safe or not, but that technique was not used back when std::vector was designed. You can see the evolution of this technique's use in std::function, which went from greedily consuming almost anything in a universal constructor to that constructor only being considered for overload resolution when it would work between C++11 and C++14.

If you want SFINAE, you cannot rely on the bodies of code like that. In order to lighten the load for compilers, compilers only have to examine declarations not definitions of functions when doing SFINAE tests.

Part of the reason is that SFINAE on expressions is hard; on entire bodies is harder. The compiler has to speculatively compile the body of the function, hit the error, then back out to the "nope, nothing was done" state.

Errors in the body of functions are always hard errors. You cannot avoid this in the current version of C++.

Now, you can write functions which decide if there will be errors or not, but don't actually have an error, then use their bodies to determine if other code will error. For example:

template<class T>
auto foo() {
  constexpr if(sizeof(T)<4) {
    return std::true_type{};
  } else {
    return std::false_type{};
}

you can use foo<char>() in some SFINAE somewhere, and its true or false-ness can make another overload substitution fail or not.

Note that the error (if any) still happens outside the body of a function (foo here).

like image 154
Yakk - Adam Nevraumont Avatar answered Oct 06 '22 01:10

Yakk - Adam Nevraumont