1

I'm searching for a way to only instantiate a class if a condition on the template type is met. I would like to do this within the class during run-/ and compile-time and not terminate the program, but throw an exception.

I'm working on a class where the template type needs to have a time component. Ideally, the type is derived from a class that only defines the time as a member. That way, doing something like the following should be safe:

#include <type_traits>
#include <stdexcept>

class Time
{
  public:
    double t;
};

class A : Time
{

};

template<class T>
class B
{
    T data;

    void do_someting()
    {
      data.t = 12.34;
    }
};

B<A> some_instance;

I found this C++ templates that accept only certain types

So the following would do the check, but static_assert only checks during compile time.

template<class T>
class B
{
    static_assert(std::is_base_of<Time, T>::value, "T must inherit from Time Class");
    T data;

    void do_someting()
    {
      data.t = 12.34;
    }
};

Would it be safe to do the following or is there a cleaner way of archiving this?

Edit: Changed due to Timos input.

template<class T>
class B
{
  B()
  {
    if(std::is_base_of<Time, T>::value)
    {
      throw std::invalid_argument( "T must inherit from Time Class" );
    }
  }

    T data;

    void do_someting()
    {
      data.t = 12.34;
    }
};
ChrisB
  • 23
  • 8
  • Your last code block cannot compile. That is not valid c++ code. Although, does the static assert work? I don't think so. And why would you even want to check at compile and runtime? The beauty of templates lies in their compile time resolution. There is no chance that your code will ever throw an exception when you already check the type at compile time. – Timo Jun 03 '18 at 08:53
  • If the check didn't fail at compile-time it can never fail at run-time. Why do you think you need to check anything at run-time and/or throw exceptions here? – super Jun 03 '18 at 08:53
  • `static_assert only checks during compile time`? This is the benefit! This prevents you from writing the wrong code as it does not compile. So no need to check this at runtime and pay for the overhead (performance and debugging cost) – JVApen Jun 03 '18 at 08:58
  • @Timo true, the last part does not compile, for some reason I can not do std::is_base_of – ChrisB Jun 03 '18 at 08:59
  • You cannot write if statements in class scope. That's why the code doesn't compile. – Timo Jun 03 '18 at 09:01
  • There are separate modules that can be added during runtime that will use class B. But I just realized that even they would need to be compiled beforehand and static_assert work just fine. – ChrisB Jun 03 '18 at 09:02
  • I changed it, thank you for the tip. – ChrisB Jun 03 '18 at 09:06
  • @ChrisB Exactly. The compile-time check (which you have coded very nicely) does _exactly_ what you want and is all that is necessary. Letting the compiler know what it can and cannot accept on your behalf is always a good thing to do, if you can. If you get this right then runtime checks are not needed. – Paul Sanders Jun 03 '18 at 09:34

3 Answers3

1

I'm a bit confused with what you try to achieve. From my understanding, you want to prevent B from being used with classes which don't inherit from Time.

For this, the solution with the static_assert is the best solution. One of the strengths of C++ is that you can cause compilation errors when a precondition gets violated. In weakly typed languages as Javascript, PHP, Bash ... this would cause a run-time error, which requires you to have a test case for that piece of code.

The other solution with the exception doesn't add any benefits, as the if-statement will be evaluated at compile time and effectively be if (true) or if (false).

So by writing the second solution, you actually need more time to debug the code as you have to launch your executable, run a test case which uses this code and check the behavior with the debugger. All of which could be prevented with the static_assert.

JVApen
  • 11,008
  • 5
  • 31
  • 67
0

Use std::enable_if which is exactly for this purpose. It will lead to compile time error if condition is not met.

#include <type_traits>
#include <stdexcept>

class Time
{
  public:
    double t;
};

class A : Time
{

};

class C
{

};

template<class T, class _T=typename std::enable_if<std::is_base_of<Time, T>::value, T>::type >
class B 
{
    T data;

    void do_someting()
    {
      data.t = 12.34;
    }
};

B<A> some_instance;
//B<C> other_instance; // error
code707
  • 1,663
  • 1
  • 8
  • 20
-1

The idea is good but implementation is wrong. This should go like this:

templacte <T, Base>
class ThrowIfNotBaseOf {
    void checkIt() {
        if(!std::is_base_of<Base, T>::value) {
            throw std::invalid_argument( "T must inherit from Time Class" );
        }
    }

public:
    TypeCheckThrow() { // default constructor
        checkIt();
    }

    TypeCheckThrow(const TypeCheckThrow<T, Base> &) { // copy constructor
        checkIt();
    }

    TypeCheckThrow(TypeCheckThrow<T, Base> &&) { // move constructor
        checkIt();
    }
};

template<class T>
class B
{
    ThrowIfNotBaseOf<T, Time> typeCheckThrow;

    T data;

    void do_someting()
    {
      data.t = 12.34;
    }
};

I'm agree that this requirement is a bit strange. Maybe you should describe what you are trying to solve instead asking how to fix your idea of solution. I'm suspecting XY Problem.

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • Absolutely not. The solution, as others have said, is to check any preconditions at _compile time_ with `static_assert`, which in fact the OP knew how to do all along but did not believe was sufficient protection. Well, it is. – Paul Sanders Jun 03 '18 at 09:29