Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dynamic_cast<> ing variable arguments to templates

I have a C++ application that executes test cases. It is possible that some test cases will depend on output from other test cases.

All test cases implement a basic interface:

/// base class for all test cases
class ITest
{
public:
    virtual void Execute() = 0;
};

Test cases that produce some object that may be useful to other test cases implement this interface:

/// implemented by test cases that provide data to other test cases
template< class Obj >
class IDependency
{
public:
    virtual Obj Get() = 0;
};

Test cases that require data from other test cases implement this interface:

/// implemented by test cases that require data from other test cases
template< class Obj >
class IDependent
{
public:

    void SetDependency( IDependency< Obj >* dependency )
    {
        dependency_ = dependency;
    };

protected:
    Obj GetDependency() const
    {
        return dependency_->Get();
    };

private:
    IDependency< Obj >* dependency_;
};

Two example test cases. One requires a const wchar_t object; one produces that object:

/// A test case that provides a "const wchar_t*" object to other test cases
class Foo : public ITest, 
            public IDependency< const wchar_t* >
{
public:
    const wchar_t* Get() 
    { 
        if( object_.length() == 0 )
            Execute();
        return object_.c_str();
    };

    virtual void Execute()
    {
        printf( "Execute Foo\n" );
        object_ = L"Object produced by Foo";
    };

private:
    std::wstring object_;
};

/// A test case that first requires a "const wchar_t*" object
class Bar : public ITest,
            public IDependent< const wchar_t* >
{
public:

    virtual void Execute()
    {
        const wchar_t* needed_object = GetDependency();

        printf( "Execute Bar with %S\n", needed_object );
    };
};

The test cases are stored in a list. Cases are added to the list by a registration process:

/// List of test cases to execute
std::vector< ITest* > list_;

/// Register a test case to execute with the system
void Register( ITest* test_case )
{
    list_.push_back( test_case );
}

Here's my problem. I wanted to implement an overload of the 'Register()' function that also accepts dependencies. But, because dependencies can be of any type (not just the 'const wchar_t*' from this example), I'm not sure how to manage this. Below is an example of more or less what I'm looking for, but I'm not sure how to make it work.

/// Register a test case with dependencies with the system
void Register( ITest* test_case, ITest* dependency, ... )
{
    IDependent< ??? >* dependent = dynamic_cast< IDependent< ??? >* >( test_case );
    IDependency< ??? >* dep = dynamic_cast< IDependency< ??? >* >( dependency );

    va_list dep_list;
    for( va_start( dep_list, dependency ); 
         NULL != dep; 
         dep = dynamic_cast< IDependency< ??? >* >( va_arg( dep_list, ITest* ) ) )
    {
        dependent->SetDependency( dep );
    }
    va_end( dep_list );

    Register( test_case );
}

An example usage:

int _tmain( int argc, _TCHAR* argv[] )
{
    /// Test case Foo
    Foo foo;

    /// Test case bar (depends on Foo)
    Bar bar;

    /// Register test case Bar with a dependency on Foo
    Register( &bar, &foo );

    /// Execute Bar. Because it depends on Foo, that will be executed first
    list_->begin()->Execute();
    return 0;
}

Expected output:

Execute Foo
Execute Bar with Object produced by Foo

Does anybody have any suggestions on how I can successfully implement this architecture? (or a better architecture that actually works?)

Thanks, PaulH

like image 439
PaulH Avatar asked Dec 29 '22 07:12

PaulH


1 Answers

I see two possible solutions.

static

Make the Register() method a template. The simple solution would be to limit the number of dependencies to some reasonable maximum.

template <class T, class D1>
void Register(T* test_case, IDependency<D1>* d1)
{
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
    // since we now know that T is a IDependent<D1>, a dynamic_cast would only be necessary
    // to allow virtual inheritance.
    static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
    Register(test_case);
}

template <class T, class D1, class D2>
void Register(T* test_case, IDependency<D1>* d1, IDependency<D2>* d2)
{
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
    static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
    Register(test_case, d2);
}

template <class T, class D1, class D2, class D3>
void Register(T* test_case, IDependency<D1>* d1, IDependency<D2>* d2, IDependency<D3>* d3)
{
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
    static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
    Register(test_case, d2, d3);
}

// ...

With compilers that support variadic templates this could probably be written in just one function template, for an unlimited amount of dependencies.

Alternatively you could make Register() return a proxy-class, so that you could write something like this:

Register(test_case)(dep1)(dep2)(dep3) /* ... */ (depN);

The proxy class would store a pointer to the container and the test-case, and define a function-call-operator that looks just like the Register(T* test_case, IDependency* d1) function in the example above, only without the "test_case" argument and the final call to Register(test_case) (which one could do in the dtor of the proxy class).

dynamic

If I understand what you're trying to do correctly, every "Dependency" can only produce one type of result. In that case you could modify the IDependency interface like this:

class IDependencyBase
{
public:
    virtual void ApplyTo(ITest* target) = 0;
};

template <class T>
class IDependency : public IDependencyBase
{
public:
    virtual void ApplyTo(ITest* target)
    {
        // cast to reference gives us an std::bad_cast if the types are not compatible,
        // which I think is a good thing here
        dynamic_cast<IDependancy<T>&>(*target).SetDependancy(this); 
    }

    virtual T Get() = 0;
};

template <class InputIterator>
void Register(ITest* test_case, InputIterator begin, InputIterator end)
{
    for (; begin != end; ++begin)
    {
        IDependancyBase* dep = *begin;
        dep->ApplyTo(test_case);
    }
    Register(test_case);
}

template <class Container>
void Register(ITest* test_case, Container deps)
{
    Register(test_case, deps.begin(), deps.end());
}

Now it might seem tempting to you to implement your varargs solution again, something like this (continuing from the second example):

void Register(ITest* test_case, ...)
{
    va_list dep_list;
    va_start(dep_list, test_case);
    while(IDependencyBase* dep = va_arg(dep_list, ITest*))
        dep->ApplyTo(test_case);

    va_end(dep_list);

    Register( test_case );
}

// and use it like

int _tmain( int argc, _TCHAR* argv[] )
{
    Foo foo;
    Bar bar;

    Register(&foo);
    Register(&bar, &foo, 0);

    list_->begin()->Execute();
    return 0;
}

However that isn't guaranteed to work. In the code above, a Foo* is stored as a varagrs argument, and read back as a IDependencyBase*. That's not guaranteed to work, since neither Foo nor IDependencyBase are PODs (IIRC they would both have to be PODs for this to be guaranteed to work -- maybe it's not guaranteed even then, I'd have to look it up in the standard). This is not some far fetched "not guaranteed by the standatd but will work everywhere" thing. Introduce multiple and/or virtual inheritance and this is almost guaranteed to fail.

So general advice when using C++: don't use varargs functions unless there is no other way. And there is always another way.

like image 182
Paul Groke Avatar answered Jan 08 '23 08:01

Paul Groke