The ambiguity is due to how using directives work.
[namespace.udir] (emphasis mine)
2 A using-directive specifies that the names in the nominated
namespace can be used in the scope in which the using-directive
appears after the using-directive. During unqualified name lookup
([basic.lookup.unqual]), the names appear as if they were declared in
the nearest enclosing namespace which contains both the
using-directive and the nominated namespace. [ Note: In this context,
“contains” means “contains directly or indirectly”. — end note ]
3 A using-directive does not add any members to the declarative
region in which it appears. [ Example:
namespace A {
int i;
namespace B {
namespace C {
int i;
}
using namespace A::B::C;
void f1() {
i = 5; // OK, C::i visible in B and hides A::i
}
}
namespace D {
using namespace B;
using namespace C;
void f2() {
i = 5; // ambiguous, B::C::i or A::i?
}
}
void f3() {
i = 5; // uses A::i
}
}
void f4() {
i = 5; // error: neither i is visible
}
— end example ]
So given your structure of namespaces, according to bit I made bold in paragraph 2, when you write
using namespace Sibling1;
It kinda translates to this
namespace /* Enclosing */ {
using Sibling1::cos;
namespace Sibling2 {
float f(float x) { return cos(x); }
};
}
The namespace I marked as enclosing is either Base or the global namespace. The "kinda" bit (according to paragraph 3) is that it's not an actual declaration being added. I.e. if something named cos already exists in /* Enclosing */, it's not a re-declaration. This is by design, because using directives can potentially bring a lot of names in, and so it shouldn't cause an error when the names they bring are not actually used.
In your case however, the name brought in is used.
When /* Enclosing */ is Base, it matches only one declaration, the one in Sibling1, as-if it was declared in Base.
When /* Enclosing */ is the global namespace, it matches the actual declaration there too, presumably the one brought in by math.h (you seem to be using that). So you get an ambiguity (the name refers to two potential entities).
So on the whole, compilers that reject this code are behaving as expected. While I understand your plight, I don't think there's really a problem for you to solve here. If client code finds Base::Sibling1::cos too verbose, it itself can employ a using directive of the form using namespace Base;. Or using a namespace alias to shorten things namespace sib1 = Base::Sibling1;.