Michael Anderson
Michael Anderson

Reputation: 73480

Disable functions inside templated class

I'm trying to disable some functions inside a simple template class. The functions that should be removed depend on whether the template argument has certain typedefs.

The example boils down to this:

template<typename T>
struct Foo
{
  typename T::Nested foo() { return typename T::Nested(); }
  int bar() { return 1; }
};


struct NoNested
{
};

struct WithNested
{
  typedef int Nested;
};

int main()
{
  Foo<WithNested> fwn;
  fwn.foo();
  fwn.bar();

  Foo<NoNested> fnn;
  //fnn.foo();
  fnn.bar();
}

However this gives me a error: no type named ‘Nested’ in ‘struct NoNested’ style error on gcc and clang++ (mind you old versions of both).

Is there an easy way to remove foo when the typedef T::Nested does not exit? (Other than template specialization of the Foo<T> class, as in the real code I have this for about 5 functions with different typedefs.. which would result in 2^5 different specialization )

EDIT: Since there has been some asking for the motivation for wanting to do this: I'd like to create something like acompile time FSM for use in a DSL.

I'd like to be able to do this

struct StateA;
struct StateB;
struct StateC;

struct StateA
{
  typedef StateB AfterNext;
};

struct StateB
{
   typedef StateA AfterPrev;
   typedef StateC AfterNext;
};

struct StateC
{
   typedef StateB AfterPrev;
};

template<typename T>
struct FSM
{
   FSM<typename T::AfterNext> next() { return FSM<T::AfterNext>(); };
   FSM<typename T::AfterPrev> prev() { return FSM<T::AfterPrev>(); };
};

So that

FSM<StateA>().next().prev().next().next();

compiles, but

FSM<StateA>().next().prev().prev();

fails.

Note that in reality there would be more transition functions than this, the transition functions would actually do something, and the FSM would store some state.

UPDATE: I've created proper examples using the methods that have been given so far. The answers vary in complexity, and while visitors method is the one I'd probably end up using (as it is simplest), my solution (the most complicated) is the only one that actually removes the function.

Upvotes: 5

Views: 1253

Answers (5)

ymett
ymett

Reputation: 2454

It is possible to choose the type T::Nested, if it exists, otherwise void, as follows.

The default choice is void:

template<class T, class = void>
struct NestedReturn
{
  typedef void type;
};

A template which always returns void, whatever type you give it:

template<class T>
struct Void
{
  typedef void type;
};

A specialisation for types with a Nested nested class by SFINAE. Note that typename Void<typename T::Nested>::type is always void, to match the default second parameter of void in the base template:

template<class T>
struct NestedReturn<T, typename Void<typename T::Nested>::type>
{
  typedef typename T::Nested type;
};

And now we use it. Note that foo() is not actually removed when there is no T::Nested, but instantiating it causes an error.

template<typename T>
struct Foo
{
  typename NestedReturn<T>::type foo() { return typename T::Nested(); }
  int bar() { return 1; }
};


struct NoNested
{
};

struct WithNested
{
  typedef int Nested;
};

int main()
{
  Foo<WithNested> fwn;
  fwn.foo();
  fwn.bar();

  Foo<NoNested> fnn;
  //fnn.foo();
  fnn.bar();
}

I suspect that using default function template parameters it would be possible to remove foo() properly using SFINAE, but that's only possible in C++11 (untested guesswork):

template<typename T>
struct Foo
{
  template<class N = T::Nested>
  N foo() { return N(); }
  int bar() { return 1; }
};

Upvotes: 1

visitor
visitor

Reputation: 1801

You could add a nested typedef to every class, such that compilation only fails when the function is instantiated.

struct null_type;  //an incomplete type, you could use a more descriptive name for your particular problem

template<typename T>
struct Foo
{
  typename T::Nested foo() { return typename T::Nested(); }
  int bar() { return 1; }
};


struct NoNested
{
   typedef null_type Nested;
};

struct WithNested
{
  typedef int Nested;
};

int main()
{
  Foo<WithNested> fwn;
  fwn.foo();
  fwn.bar();

  Foo<NoNested> fnn;
  //fnn.foo();  //attempt to use incomplete type when used
  fnn.bar();
}

Upvotes: 1

Johan R&#229;de
Johan R&#229;de

Reputation: 21378

You can use class template specialization. If you have several functions, then you can move each function to a base class, and specialize each base class.

Upvotes: 4

Werolik
Werolik

Reputation: 965

Try making function foo template itself. It will compile only when called, so you will get the error only when you will try calling it with NoNested class.

Upvotes: 1

Michael Anderson
Michael Anderson

Reputation: 73480

Here's how I think I can solve it. It's inspired by user763305's comments. It requires 2*N specialisations rather than 2^N.

template <typename T>
struct has_nested {
  // Variables "yes" and "no" are guaranteed to have different sizes,
  // specifically sizeof(yes) == 1 and sizeof(no) == 2.
  typedef char yes[1];
  typedef char no[2];

  template <typename C>
    static yes& test(typename C::Nested*);

  template <typename>
    static no& test(...);

  // If the "sizeof" the result of calling test<T>(0) would be equal to the sizeof(yes),
  // the first overload worked and T has a nested type named type.
  static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};



template<typename T>
struct FooBase
{
  int bar() { return 1; }
};

template<typename T, bool>
struct FooImpl : public FooBase<T>
{
};

template<typename T>
struct FooImpl<T,true> : public FooBase<T>
{
  typename T::Nested foo() { return typename T::Nested(); }
};


template<typename T>
struct Foo : public FooImpl<T, has_nested<T>::value >
{
};


struct NoNested
{
};

struct WithNested
{
  typedef int Nested;
};

int main()
{
  Foo<WithNested> fwn;
  fwn.foo();
  fwn.bar();

  Foo<NoNested> fnn;
  //fnn.foo();
  fnn.bar();

}

Upvotes: 0

Related Questions