1

today i face a problem that hurts my peace of mind. I have resumed my problem in a very smart and meaningfull example where the expected result is not met although no error is generated.

#include <iostream>


namespace MATH {


template <std::size_t M, std::size_t N, class T>
class Matrix
{

};

void invert(Matrix<2, 2, double>& m)
{
    std::cout << "DIM 2 : MATH version (use of the determinant)" << std::endl;
}

void invert(Matrix<3, 3, double>& m)
{
    std::cout << "DIM 3 : MATH version (use of the determinant)" << std::endl;
}


}


namespace GEOM {


template <std::size_t N>
using Matrix = MATH::Matrix<N, N, double>;// orthonormal set of vectors

template <std::size_t N>
void invert(Matrix<N>& m)
{
    std::cout << "DIM " << N << " : GEOM version (use of the transpose)" << std::endl;
}

void geom_foo_purpose(Matrix<3>& m)
{
    invert(m);
}


}


int main(int argc, char **argv)
{
    GEOM::Matrix<3> m;
    GEOM::geom_foo_purpose(m);

    return 0;
}

output : std::cout << "DIM 3 : MATH version (use of the determinant)" << std::endl;

In geom_foo_purpose definition, the call to invert results in an unqualified id because a template deduction is requested. So, the ADL is able to say : ok, let's look at the MATH namespace. The fact that allows MATH::invert to be prefered to the GEOM::invert version because the non template version has priority is inadmissible in this case i think.

For example, i want to develop the GEOM content first, by defining the Matrix class as a GEOM type. GEOM::invert is called. No problem. One day, i want to generalize my Matrix class in another namespace MATH and i could think : ok, i keep the same methods and i don't break the code. Just put a using MATH::Matrix... And i become unable to understand where is the performance overhead nor why some sensitive measures change.

So i actually think about three solutions :

  • verify each namespaces when i add a function or when i change a using
  • specify the namespace for each call
  • rely on ambiguous call compiler errors, when detected

Is there a decent way to overcome this ?

Cevik
  • 313
  • 1
  • 4
  • 17

2 Answers2

1

The problem is that your GEOM::Matrix is only a synonym of MATH::Matrix, and not a new type in GEOM. This is the intended effect of a using statement, and therefore the argument dependent name lookup finds the MATH::invert() as the best match.

If you want to fix this, define a real GEOM::Matrix this way:

    template <std::size_t N>
    class Matrix : public MATH::Matrix<N, N, double> {};// orthonormal set of vectors

Here an online demo.

Edit:

There's a fundamental design issue that you have to decide on:

  • Either you want to have distinct (yet somewhat interchangeable) Matrix types and benefit from ADL. In this case you manage two types and can have of course to manage some conversions (from GEOM to MATH).
  • Or you want to have one single Matrix type (defined in one namespace) with specialisation for some parameters (in the same namespace, i.e. the transposition inversion would migrate to MATH).

Boh have their advantages and inconvenience. The choice is yours. Personally I'd prefer the second option: all the matrix specific operations would then better isolated. And as you say, a matrix is a matrix. But apparently, you've choosen the second option, and my answer tries to provide you some solutions for that.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Wouldn't public inheritance be more adequate? – Ralph Tandetzky Dec 30 '15 at 23:12
  • demo with simplified example: http://coliru.stacked-crooked.com/a/3385311075d2e1d4 – YSC Dec 30 '15 at 23:13
  • I have think to this solution a lot.. But i don't want to fall into the inheritance category because i prefer to keep the liberty to mix MATH::Matrix and GEOM::Matrix without any cast. It's the same problem with GEOM::Vector and GEOM::Point. They are both vectors and can be used in the same contexts. – Cevik Dec 30 '15 at 23:17
  • ADL still applies though... It is just that now, the template method is an exact match (in this case). – Jarod42 Dec 30 '15 at 23:19
  • @Cevik you can still mix as you want: you can for example perfectly add to your code snippet something like: `MATH::invert(m);` – Christophe Dec 30 '15 at 23:26
  • @Cevik: [Demo](http://coliru.stacked-crooked.com/a/2dfbbd8d1b084c76) where there is still ambiguity. As ADL takes into account base class too. – Jarod42 Dec 30 '15 at 23:27
  • @Christophe : if i define MATH::Matrix foo(const MATH::Matrix& m), with inheritance, i must implement GEOM::Matrix foo(const GEOM::Matrix& m) because a value is returned... – Cevik Dec 30 '15 at 23:32
  • 1
    @Cevik yes. You have to decide in your design whether you want to have GEOM specific matrixes (and use this deifference in name lookup, at the cost of some extra work) or if you prefer to use only one type of Matrixes but have to be more explicit in the function calls. – Christophe Dec 30 '15 at 23:40
  • This make sense indeed. – Cevik Dec 30 '15 at 23:42
  • I don't think that inheritance is a proper design to solve the problem. It leads to all kinds of problems including weird asymmetries. It violates Liskov's substitution principle. – Ralph Tandetzky Dec 30 '15 at 23:48
  • @RalphTandetzky: Moreover, it doesn't solve completely the problem (see demo link above). – Jarod42 Dec 30 '15 at 23:51
  • @RalphTandetzky I've edited my answer to make clear my personal position which would not use this inheritance approach. Nevertheless, if OP want to handle geometric matrixes as a distinct type, there are not so many different solutions. – Christophe Dec 31 '15 at 00:14
  • @Jarod42 your demo is very interesting ! I'm aware that my solution will not solve all ambiguities. I edited my answer to clarify my position about the intended design, beyond the OP's specific question. – Christophe Dec 31 '15 at 00:20
  • 1
    With the second option, it would be interesting to define what is an "orthogonal" matrix inside MATH namespace in order to define invert and its specialization only one time :) In fact, i've read this topic that explain (particulary the Nawaz comment) how ADL should be used. In my case, i don't need ADL because i define GEOM as a more specialized code and not as a "generic overload" code. http://stackoverflow.com/questions/2958648/what-are-the-pitfalls-of-adl – Cevik Dec 31 '15 at 00:33
0

General advice

It is best practice to keep classes and functions that are designed to work together in one namespace to enable ADL. (See Item #57 in C++ Coding Standards: Keep a type and its nonmember function interface in the same namespace.) Sometimes it is recommended to keep unrelated classes and functions in separate namespaces. (See Item #58 in the same book.) I find this too restrictive, because it creates a huge namespace jungle very quickly.

Common practice

It is more common practice to put all stuff that belongs logically to one library into one namespace. It's the libraries responsibility to avoid internal name clashes. Users of the library should usually not add new stuff to a libraries namespace. That's pretty much how the standard library, boost, OpenCV and a host of other libraries work. That's what I recommend. Don't try to overseparate. It just hurts your head and is not necessary.

Namespace usage

These common libraries use nested namespaces for some purposes, like boost::detail for implementation details, that should not be of interest to the user of the library. Other examples where more namespaces are necessary are

  • inline namespaces for versioning and
  • namespaces to select a set of user-defined literals and
  • possibly some others.

EDIT:

Advice for your situation

If I understand correctly, your GEOM::Matrix is supposed to be an orthogonal matrix. (I just noticed that very late and would suggest a clearer name for it.) That should be a different type, this type has to ensure the internal invariant of being orthogonal. Using using or typedef just does aliasing and there's no way for you to guarantee, that the invariant if broken through some MATH::Matrix functions. Therefore, I would suggest composition as the means to solve the problem:

namespace GEOM
{

template <std::size_t N, typename T>
class OrthogonalMatrix
{
private:
    MATH::Matrix<N,N,T> mat;

public:
    // Enable use of const interface of MATH::Matrix<N,N,T>. 
    operator const MATH::Matrix<N,N,T> &() const { return mat; }

    // ... here goes the implementation ensuring 
    // orthogonality at all times.
};

} // end of namespace GEOM

An alternative would be private inheritance, which enables you to make MATH::Matrix methods public selectively with the keyword using like this:

namespace GEOM
{

template <std::size_t N, typename T>
class OrthogonalMatrix : private MATH::Matrix<N,N,T>;
{
public:
    // Make `MATH::Matrix<N,N,T>::transposeInPlace` publicly accessible
    using MATH::Matrix<N,N,T>::transposeInPlace;

    // Enable use of const interface of MATH::Matrix<N,N,T>. 
    operator const MATH::Matrix<N,N,T> &() const { return *this; }

    // ... here goes the implementation ensuring 
    // orthogonality at all times.
};

} // end of namespace GEOM

You should not do that with the constructor or entry access functions, since they would enable the user of the class to break invariants which can easily lead to a lot of confusion. Also public inheritance plus using in the private section would be wrong, because the user of the class would still be able to break invariants by converting to MATH::Matrix<N,N,T> & and messing up the matrix then.

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • It is too bad that we have to do concessions with namespaces. – Cevik Dec 30 '15 at 23:38
  • Namespaces are a pretty cool thing. And one can create great theories and all about them. But at the end it's important what is practical. I tried to give some proven practical advice. – Ralph Tandetzky Dec 30 '15 at 23:43
  • Thank you ! But i don't understand how the libraries can take "responsibility to avoid internal name clashes" without explicitly using the namespace prefix every time (that's not the goal of the ADL right) ? – Cevik Dec 30 '15 at 23:47
  • @RalphTandetzky: When using third part library (or writing template), you have to take care to not introduce name clash with ADL though :-/ – Jarod42 Dec 30 '15 at 23:47
  • @Cevik Yep. That's a problem. Libraries assume that the global namespace is not littered, or alternatively, they have to prefix everything with their namespace. This is done for STL implementations, but for other libraries it is not common usage since it defeats the pupose of creating the namespace. – Ralph Tandetzky Dec 30 '15 at 23:53